<cfcomponent>
	<!--- *************************************************
	Integrity/existence/uniqueness functions
	************************************************* --->
	<cffunction name="isPBSScholarshipType" output="false" access="public" returntype="boolean">
		<cfargument name="Scholarship_Type" type="string" required="yes" />
		<cfif ListFindNoCase(getPBSScholarshipTypes(), arguments.Scholarship_Type) gt 0>
			<cfreturn true />
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	
	<cffunction name="isNonPBSScholarshipType" output="false" access="public" returntype="boolean">
		<cfargument name="Scholarship_Type" type="string" required="yes" />
		<cfif ListFindNoCase(getNonPBSScholarshipTypes(), arguments.Scholarship_Type) gt 0>
			<cfreturn true />
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	
	<cffunction name="hasPBSScholarshipType" output="false" access="public" returntype="boolean">
		<cfargument name="scholarshipTypes" type="string" required="yes" />
		<cfset var scholarshipType = "" />
		<cfloop index="scholarshipType" list="#arguments.scholarshipTypes#">
			<cfif ListFindNoCase(getPBSScholarshipTypes(), scholarshipType) gt 0>
				<cfreturn true />
			</cfif>
		</cfloop>
		<cfreturn false />
	</cffunction>
	
	<cffunction name="hasNonPBSScholarshipType" output="false" access="public" returntype="boolean">
		<cfargument name="scholarshipTypes" type="string" required="yes" />
		<cfset var scholarshipType = "" />
		<cfloop index="scholarshipType" list="#arguments.scholarshipTypes#">
			<cfif ListFindNoCase(getNonPBSScholarshipTypes(), scholarshipType) gt 0>
				<cfreturn true />
			</cfif>
		</cfloop>
		<cfreturn false />
	</cffunction>

	<cffunction name="isWorkshopSeason" output="false" access="public" returntype="boolean" hint="Workshop season is between September 1 and March 2">
		<cfscript>
			var ret = false;
			var dateNow = getDateNow();
			var py = getCurrentProgramYear();
			var openDate = ListFirst(py, "-") & "0901";
			var closeDate = ListLast(py, "-") & "0302";
			if (dateNow gte openDate AND dateNow lte closeDate) {
				ret = true;
			}
			return ret;
		</cfscript>
	</cffunction>
	
	<cffunction name="isOrderSeason" output="false" access="public" returntype="boolean" hint="Order season is between September 1 and January 31">
		<cfscript>
			var ret = false;
			var dateNow = getDateNow();
			var py = getCurrentProgramYear();
			var openDate = ListFirst(py, "-") & "0901";
			var closeDate = ListLast(py, "-") & "0131";
			if (dateNow gte openDate AND dateNow lte closeDate) {
				ret = true;
			}
			return ret;
		</cfscript>
	</cffunction>
	
	<cffunction name="isFundSeason" output="false" access="public" returntype="boolean" hint="Fund season is between April 1 and May 1">
		<cfscript>
			var ret = false;
			var dateNow = getDateNow();
			var openDate = Year(Now()) & "0401";
			var closeDate = Year(Now()) & "0501";
			if (Year(Now()) eq "2015") {
				closeDate = "20150801";
			}			
			if (dateNow gte openDate AND dateNow lte closeDate) {
				ret = true;
			}
			return ret;
		</cfscript>
	</cffunction>

	<cffunction name="isUserEmailUnique" output="false" access="public" returntype="boolean">
		<cfargument name="userID" type="numeric" required="yes" />
		<cfargument name="email" type="string" required="yes" />
		<cfset var isUnique = true />
		<cfinvoke component="assets.calgrants.com.gateway.UserGateway" method="query" returnvariable="users">
			<cfinvokeargument name="Email" value="#arguments.email#" />
		</cfinvoke>
		<cfloop query="#users#">
			<cfif userID neq User_ID>
				<cfset isUnique = false />
			</cfif>
		</cfloop>
		<cfreturn isUnique />
	</cffunction>
	
	<cffunction name="isScholarEmailUnique" output="false" access="public" returntype="boolean">
		<cfargument name="Scholarship_ID" type="numeric" required="yes" />
		<cfargument name="Email" type="string" required="yes" />
		<cfset var isUnique = true />
		<cfinvoke component="assets.calgrants.com.gateway.ScholarshipGateway" method="query" returnvariable="scholarships">
			<cfinvokeargument name="Email" value="#arguments.email#" />
		</cfinvoke>
		<cfloop query="#scholarships#">
			<cfif arguments.Scholarship_ID neq Scholarship_ID>
				<cfset isUnique = false />
			</cfif>
		</cfloop>
		<cfreturn isUnique />
	</cffunction>
	
	<cffunction name="isValidClaimID" output="false" access="public" returntype="boolean">
		<cfargument name="Scholarship_Type" type="string" required="yes" />
		<cfif ListFindNoCase(getPBSScholarshipTypes(), arguments.Scholarship_Type) gt 0>
			<cfreturn true />
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	<!--- *************************************************
	Formatting functions
	************************************************* --->
	<cffunction name="formatCase" output="false" access="public" returntype="any">
		<cfargument name="objects" type="string" required="yes" hint="List of objects to be formatted for case" />
		<cfargument name="objectArgs" type="any" required="no" default="" />
		<!---
			Apply appropriate case (capitalization) to applicable property values for each object in arguments.objects
			
			Objects available for this function include:
			
			Evaluation - Q8 (high school, city)
			Fund - Payee_First_Name, Payee_Last_Name, Payee_Organization, Payee_Department, Payee_Address, Payee_City, Payee_State, Payee_Email
			Institution - Institution, FAO_First_Name, FAO_Last_Name, FAO_Title, FAO_Email, FAO_Address1, FAO_Address2, FAO_City, FAO_State, Payment_First_Name, Payment_Last_Name, 
				Payment_Title, Payment_Email, Payment_Address1, Payment_Address2, Payment_City, Payment_State
			Order - Shipping_Organization, Shipping_Department, Shipping_Name, Shipping_Address, Shipping_City, Shipping_State
			Organization - Organization, Address, City
			Scholarship - First_Name, Last_Name, Address, City, Email, High_School
			Site - Site, Address, City
			Student - First_Name, Last_Name, Address, City, Email, First_Name_Alt1, Last_Name_Alt1, Address_Alt1, City_Alt1, Email_Alt1 (repeat for _Alt2)
			User - First_Name, Last_Name, Title, Email
			Volunteer - First_Name, Last_Name, Title, Organization, Address, City, Email
			
			Arguments
			
			objects - Comma separate list of object to process (eg. student, evaluation, user)
			objectsArgs - Struct where key is object list member from arguments.objects and value is struct of arguments passed as an argumentCollection to
			the object's gateway query() function (eg. student = {programYears = "2014-2015", workshopIDs = "5555,5556,5557"})
		--->
		<cfscript>
			/*
			var i = 0;
			var object = "";
			var args = "";
			var gateway = "";
			var recs = "";
			var recArray = "";
			var rec = "";
			var _rec = "";
			var retArray = ArrayNew(1);
			for (i=1; i lte ListLen(arguments.objects); i=i+1) {
				object = ListGetAt(arguments.objects, i);
				args = StructNew();
				if (IsStruct(arguments.objectArgs) AND StructKeyExists(arguments.objectArgs, object)) {
					args = arguments.objectArgs[object];
				}
				gateway = CreateObject("component", "assets.calgrants.com.gateway." & object & "Gateway");
				recs = gateway.query(argumentCollection = args);
				recArray = queryToArray(recs);
				for (r=1; r lte ArrayLen(recArray); r=r+1) {
					rec = Duplicate(recArray[r]);
					_rec = StructNew();
					_rec = Duplicate(recArray[r]);
					switch (object) {
						case "evaluation":
							//Q8 (high school, city)
							_rec.Q8 = titleCase(rec.Q8);
						break;
						case "fund":
							//Payee_First_Name, Payee_Last_Name, Payee_Organization, Payee_Department, Payee_Address, Payee_City, Payee_State, Payee_Email
							_rec.Payee_First_Name = titleCase(rec.Payee_First_Name);
							_rec.Payee_Last_Name = titleCase(rec.Payee_Last_Name);
							_rec.Payee_Organization = titleCase(rec.Payee_Organization);
							_rec.Payee_Department = titleCase(rec.Payee_Department);
							_rec.Payee_Address = titleCase(rec.Payee_Address);
							_rec.Payee_City = titleCase(rec.Payee_City);
							_rec.Payee_State = titleCase(rec.Payee_State);
							_rec.Payee_Email = LCase(rec.Payee_Email);
						break;
						case "institution":
							//Institution, FAO_First_Name, FAO_Last_Name, FAO_Title, FAO_Email, FAO_Address1, FAO_Address2, FAO_City, FAO_State, 
							//Payment_First_Name, Payment_Last_Name, Payment_Title, Payment_Email, Payment_Address1, Payment_Address2, Payment_City, Payment_State
							_rec.Institution = titleCase(rec.Institution);
							_rec.FAO_First_Name = titleCase(rec.FAO_First_Name);
							_rec.FAO_Last_Name = titleCase(rec.FAO_Last_Name);
							_rec.FAO_Title = titleCase(rec.FAO_Title);
							_rec.FAO_Email = LCase(rec.FAO_Email);
							_rec.FAO_Address1 = titleCase(rec.FAO_Address1);
							_rec.FAO_Address2 = titleCase(rec.FAO_Address2);
							_rec.FAO_City = titleCase(rec.FAO_City);
							_rec.FAO_State = titleCase(rec.FAO_State);
							_rec.Payment_First_Name = titleCase(rec.Payment_First_Name);
							_rec.Payment_Last_Name = titleCase(rec.Payment_Last_Name);
							_rec.Payment_Title = titleCase(rec.Payment_Title);
							_rec.Payment_Email = LCase(rec.Payment_Email);
							_rec.Payment_Address1 = titleCase(rec.Payment_Address1);
							_rec.Payment_Address2 = titleCase(rec.Payment_Address2);
							_rec.Payment_City = titleCase(rec.Payment_City);
							_rec.Payment_State = titleCase(rec.Payment_State);
						break;
						case "order":
							//Shipping_Organization, Shipping_Department, Shipping_Name, Shipping_Address, Shipping_City, Shipping_State
							_rec.Shipping_Organization = titleCase(rec.Shipping_Organization);
							_rec.Shipping_Department = titleCase(rec.Shipping_Department);
							_rec.Shipping_Name = titleCase(rec.Shipping_Name);
							_rec.Shipping_Address = titleCase(rec.Shipping_Address);
							_rec.Shipping_City = titleCase(rec.Shipping_City);
							_rec.Shipping_State = titleCase(rec.Shipping_State);
						break;
						case "organization":
							_rec.Organization = titleCase(rec.Organization);
							_rec.Address = titleCase(rec.Address);
							_rec.City = titleCase(rec.City);
						break;
						case "scholarship":
							//First_Name, Last_Name, Address, City, Email, High_School
							_rec.First_Name = titleCase(rec.First_Name);
							_rec.Last_Name = titleCase(rec.Last_Name);
							_rec.Address = titleCase(rec.Address);
							_rec.City = titleCase(rec.City);
							_rec.Email = LCase(rec.Email);
							_rec.High_School = titleCase(rec.High_School);
						break;
						case "site":
							_rec.Site = titleCase(rec.Site);
							_rec.Address = titleCase(rec.Address);
							_rec.City = titleCase(rec.City);
						break;
						case "student":
							//First_Name, Last_Name, Address, City, Email, First_Name_Alt1, Last_Name_Alt1, Address_Alt1, City_Alt1, Email_Alt1 (repeat for _Alt2)
							_rec.First_Name = titleCase(rec.First_Name);
							_rec.Last_Name = titleCase(rec.Last_Name);
							_rec.Address = titleCase(rec.Address);
							_rec.City = titleCase(rec.City);
							_rec.Email = LCase(rec.Email);
							_rec.First_Name_Alt1 = titleCase(rec.First_Name_Alt1);
							_rec.Last_Name_Alt1 = titleCase(rec.Last_Name_Alt1);
							_rec.Address_Alt1 = titleCase(rec.Address_Alt1);
							_rec.City_Alt1 = titleCase(rec.City_Alt1);
							_rec.Email_Alt1 = LCase(rec.Email_Alt1);
							_rec.First_Name_Alt2 = titleCase(rec.First_Name_Alt2);
							_rec.Last_Name_Alt2 = titleCase(rec.Last_Name_Alt2);
							_rec.Address_Alt2 = titleCase(rec.Address_Alt2);
							_rec.City_Alt2 = titleCase(rec.City_Alt2);
							_rec.Email_Alt2 = LCase(rec.Email_Alt2);
						break;
						case "user":
							//First_Name, Last_Name, Title, Email
							_rec.First_Name = titleCase(rec.First_Name);
							_rec.Last_Name = titleCase(rec.Last_Name);
							_rec.Title = titleCase(rec.Title);
							_rec.Email = LCase(rec.Email);
						break;
						case "volunteer":
							//First_Name, Last_Name, Title, Organization, Address, City, Email
							_rec.First_Name = titleCase(rec.First_Name);
							_rec.Last_Name = titleCase(rec.Last_Name);
							_rec.Title = titleCase(rec.Title);
							_rec.Organization = titleCase(rec.Organization);
							_rec.Address = titleCase(rec.Address);
							_rec.City = titleCase(rec.City);
							_rec.Email = LCase(rec.Email);
						break;
						default:
							_rec = "no object";
					}
					ArrayAppend(retArray, _rec);
					if (r eq 10) {
						break;
					}
				}
			}
			return retArray;
			*/
		</cfscript>
		<cfreturn "Not implemented" />
	</cffunction>
	
	<cffunction name="titleCase" output="false" access="public" returntype="string">
		<cfargument name="str" type="string" required="yes" />
		<cfscript>
		   var j = 1; 
		   var m = 1;
		   var doCap = true;
		   var tempVar = "";
		   /* Make each word in text an array variable */
		   var Words = ListToArray(LCase(Trim(arguments.str)), " ");
		   var excludeWords = StructNew();
		   var ResetToALLCAPS = StructNew();
		   /* Words to never capitalize */
		   //tempVar =  ListToArray("a,above,after,ain't,among,an,and,as,at,below,but,by,can't,don't,for,from,from,if,in,into,it's,nor,of,off,on,on,onto,or,over,since,the,to,under,until,up,with,won't");
		   tempVar = ListToArray("of,and");
		   for(j=1; j LTE (ArrayLen(tempVar)); j = j+1){
				   excludeWords[tempVar[j]] = 0;
		   }
		   /* Words to always capitalize */
		   tempVar = ListToArray("II,III,IV,V,VI,VII,VIII,IX,X,XI,XII,XIII,XIV,XV,XVI,XVII,XVIII,XIX,XX,XXI,po,pob,nw,ne,sw,se,p.o.,po.");
		   for(j=1; j LTE (ArrayLen(tempVar)); j = j+1){
				   ResetToALLCAPS[tempVar[j]] = 0;
		   }
		   /* Check words against exclude list */
		   for(j=1; j LTE (ArrayLen(Words)); j = j+1){
				   doCap = true;
				   /* Word must be less than four characters to be in the list of excluded words */
				   if(LEN(Words[j]) LT 4){
						   if(StructKeyExists(excludeWords,Words[j])){ doCap = false; }
				   }
				   /* Capitalize hyphenated words */
				   if(ListLen(trim(Words[j]),"-") GT 1){
						   for(m=2; m LTE ListLen(Words[j], "-"); m=m+1){
								   tempVar = ListGetAt(Words[j], m, "-");
								   tempVar = UCase(Mid(tempVar,1, 1)) & Mid(tempVar,2, Len(tempVar)-1);
								   Words[j] = ListSetAt(Words[j], m, tempVar, "-");
						   }
				   }
				   /* Automatically capitalize first and last words */
				   if(j eq 1 or j eq ArrayLen(Words)){ doCap = true; }
				   /* Capitalize qualifying words */
				   if(doCap){ Words[j] = UCase(Mid(Words[j],1, 1)) & Mid(Words[j],2, Len(Words[j])-1); }
				   if (structKeyExists(ResetToALLCAPS, Words[j])) Words[j] = UCase(Words[j]);
		   }
		   return ArrayToList(Words, " ");
		</cfscript>
	</cffunction>
	
	<cffunction name="formatPhone" output="false" access="public" returntype="string">
		<cfargument name="phone" type="string" required="yes" />
		<cfset var fPhone = "">
		<cfset var ret = "">
		<cfset fPhone = Replace(arguments.phone, "(", "", "all")>
		<cfset fPhone = Replace(fPhone, ")", "", "all")>
		<cfset fPhone = Replace(fPhone, " ", "", "all")>
		<cfset fPhone = Replace(fPhone, "-", "", "all")>
		<cfset fPhone = Replace(fPhone, ".", "", "all")>
		<cfif len(fPhone) gte 10>
			<cfset fPhone = Right(fPhone, 10)>
			<cfset ret = Left(fPhone, 3) & "-" & Mid(fPhone, 4, 3) & "-" & Right(fPhone, 4)>
		</cfif>
		<cfreturn ret>
	</cffunction>

	<cffunction name="formatShortDate" output="false" access="public" returntype="string">
		<cfargument name="dateVal" required="yes" />
		<cfif IsDate(arguments.dateVal)>
			<cfreturn DateFormat(arguments.dateVal, "m/d/yyyy") />
		<cfelse>
			<cfreturn "" />
		</cfif>
	</cffunction>

	<cffunction name="formatZipCode" output="false" access="public" returntype="string">
		<cfargument name="zipCode" type="string" required="yes" />
		<cfif NOT IsValid("zipcode", Trim(arguments.zipCode))>
			<cfreturn "" />
		<cfelse>
			<cfreturn Left(Trim(arguments.zipCode), 5) />
		</cfif>
	</cffunction>
	
	<cffunction name="formatSSN" output="false" access="public" returntype="string">
		<cfargument name="ssn" type="string" required="yes" />
		<cfset var preSSN = toNumber(arguments.ssn) />
		<cfset var postSSN = "">
		<cfif Len(preSSN) neq 9>
			<cfreturn "" />
		<cfelse>
			<cfset postSSN = Left(preSSN, 3) & "-" & Mid(preSSN, 4, 2) & "-" & Right(preSSN, 4) />
			<cfreturn postSSN />
		</cfif>
	</cffunction>
	
	<cffunction name="stripHTML" output="false" access="public" returntype="string">
		<cfargument name="str" required="yes" />
		<cfset var ret = arguments.str />
		<cfset ret = REReplaceNoCase(ret, "<[^>]*(?:>|$)", "", "all") />
		<cfreturn ret />
	</cffunction>
	<!--- *************************************************
	Conversion functions
	************************************************* --->
	<cffunction name="passbackToQueryString" output="false" access="public" returntype="string">
		<cfargument name="passbackString" type="string" required="false" default="" />
		<cfset var queryString = "" />
		<cfset var loopIndex = 1 />
		<cfif Len(Trim(arguments.passbackString)) gt 0>
			<cfloop index="kvp" list="#arguments.passbackString#" delimiters=";">
				<cfif loopIndex gt 1>
					<cfset queryString = queryString & "&" />
				</cfif>
				<cfset queryString = queryString & ListFirst(kvp) & "=" & ListLast(kvp) /> 
			</cfloop>
			<cfset loopIndex = loopIndex + 1 />
		</cfif>
		<cfreturn queryString />
	</cffunction>
	
	<cffunction name="wddxToCFML" output="false" access="public" returntype="any">
		<cfargument name="wddx" type="string" required="true" />
		<cfset var cfml = "" />
		<cfwddx action="wddx2cfml" input="#arguments.wddx#" output="cfml" />
		<cfreturn cfml />
	</cffunction>
	
	<cffunction name="queryToCSV" output="false" access="public" returntype="string" hint="I take a query and convert it to a comma separated value string.">
		<cfargument name="query" type="query" required="true" hint="I am the query being converted to CSV."	/>
		<cfargument name="fields" type="string" required="true" hint="I am the list of query fields to be used when creating the CSV value." />
		<cfargument name="createHeaderRow" type="boolean" required="false" default="true" hint="I flag whether or not to create a row of header values." />
		<cfargument name="delimiter" type="string" required="false" default="," hint="I am the field delimiter in the CSV value." />
		<!--- Define the local scope. --->
		<cfset var local = {} />
		<!---
		First, we want to set up a column index so that we can
		iterate over the column names faster than if we used a
		standard list loop on the passed-in list.
		--->
		<cfset local.columnNames = [] />
		<!---
		Loop over column names and index them numerically. We
		are working with an array since I believe its loop times
		are faster than that of a list.
		--->
		<cfloop index="local.columnName" list="#arguments.fields#" delimiters=",">
			<!--- Store the current column name. --->
			<cfset ArrayAppend(local.columnNames, Trim(local.columnName)) />
		</cfloop>
		<!--- Store the column count. --->
		<cfset local.columnCount = ArrayLen(local.columnNames) />
		<!---
		Now that we have our index in place, let's create
		a string buffer to help us build the CSV value more
		effiently.
		--->
		<cfset local.buffer = CreateObject("java", "java.lang.StringBuffer").Init() />
		<!--- Create a short hand for the new line characters. --->
		<cfset local.newLine = (chr(13) & chr(10)) />
		<!--- Check to see if we need to add a header row. --->
		<cfif arguments.createHeaderRow>
			<!--- Create array to hold row data. --->
			<cfset local.rowData = [] />
			<!--- Loop over the column names. --->
			<cfloop index="local.columnIndex" from="1" to="#local.columnCount#" step="1">
				<!--- Add the field name to the row data. --->
				<cfset local.rowData[local.columnIndex] = """#local.columnNames[ local.columnIndex ]#""" />
			</cfloop>
			 
			<!--- Append the row data to the string buffer. --->
			<cfset local.buffer.Append(JavaCast("string",(ArrayToList(local.rowData, arguments.delimiter) & local.newLine))) />
		</cfif>
		<!---
		Now that we have dealt with any header value, let's
		convert the query body to CSV. When doing this, we are
		going to qualify each field value. This is done be
		default since it will be much faster than actually
		checking to see if a field needs to be qualified.
		--->
		<!--- Loop over the query. --->
		<cfloop query="arguments.query">
			<!--- Create array to hold row data. --->
			<cfset local.rowData = [] />
			<!--- Loop over the columns. --->
			<cfloop index="local.columnIndex" from="1" to="#local.columnCount#" step="1">
				<!--- Add the field to the row data. --->
				<cfset local.rowData[local.columnIndex] = """#Replace(arguments.query[local.columnNames[local.columnIndex]][arguments.query.CurrentRow], """", """""", "all" )#""" />
			</cfloop>
			<!--- Append the row data to the string buffer. --->
			<cfset local.buffer.Append(JavaCast("string",(ArrayToList(local.rowData, arguments.delimiter) & local.newLine))) />
		</cfloop>
		<!--- Return the CSV value. --->
		<cfreturn local.buffer.ToString() />
	</cffunction>
	
	<cffunction name="queryToArray" output="false" access="public" returntype="any"
    hint="Converts CF query records to structs. Returns a single struct (1 record) or an array of structs (>1 record).">
		<cfargument name="Query" type="query" required="true" />
		<cfargument name="Row" type="numeric" required="false" default="0" />
		<cfscript>
			return queryToStruct(argumentCollection = arguments);
		</cfscript>
    </cffunction>
	
	<cffunction name="queryToStruct" output="false" access="public" returntype="any"
    hint="Converts CF query records to structs. Returns a single struct (1 record) or an array of structs (>1 record).">
		<!--- Define arguments. --->
		<cfargument name="Query" type="query" required="true" />
		<cfargument name="Row" type="numeric" required="false" default="0" />
		<cfscript>
			// Define the local scope.
			var LOCAL = StructNew();
			// Determine the indexes that we will need to loop over.
			// To do so, check to see if we are working with a given row,
			// or the whole record set.
			if (ARGUMENTS.Row){
				// We are only looping over one row.
				LOCAL.FromIndex = ARGUMENTS.Row;
				LOCAL.ToIndex = ARGUMENTS.Row;
			} else {
				// We are looping over the entire query.
				LOCAL.FromIndex = 1;
				LOCAL.ToIndex = ARGUMENTS.Query.RecordCount;
			}
			// Get the list of columns as an array and the column count.
			LOCAL.Columns = ListToArray( LCase(ARGUMENTS.Query.ColumnList) );
			LOCAL.ColumnCount = ArrayLen( LOCAL.Columns );
			// Create an array to keep all the objects.
			LOCAL.DataArray = ArrayNew( 1 );
			// Loop over the rows to create a structure for each row.
			for (LOCAL.RowIndex = LOCAL.FromIndex ; LOCAL.RowIndex LTE LOCAL.ToIndex ; LOCAL.RowIndex = (LOCAL.RowIndex + 1)){
				// Create a new structure for this row.
				ArrayAppend( LOCAL.DataArray, StructNew() );
				// Get the index of the current data array object.
				LOCAL.DataArrayIndex = ArrayLen( LOCAL.DataArray );
				// Loop over the columns to set the structure values.
				for (LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE LOCAL.ColumnCount ; LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1)){
					// Get the column value.
					LOCAL.ColumnName = LOCAL.Columns[ LOCAL.ColumnIndex ];
					// Set column value into the structure.
					if (IsWDDX(ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ])) {
						LOCAL.DataArray[ LOCAL.DataArrayIndex ][ LOCAL.ColumnName ] = DeserializeWDDX(ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ]);
					} else if (ListLast(LOCAL.ColumnName, "_") eq "date") {
						LOCAL.DataArray[ LOCAL.DataArrayIndex ][ LOCAL.ColumnName ] = DateFormat(ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ], "m/d/yyyy");
					} else if (LOCAL.ColumnName contains "date_time") {
						LOCAL.DataArray[ LOCAL.DataArrayIndex ][ LOCAL.ColumnName ] = DateFormat(ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ], "m/d/yyyy") & " " & TimeFormat(ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ], "h:mmtt");
					} else if (ListLast(LOCAL.ColumnName, "_") eq "time") {
						LOCAL.DataArray[ LOCAL.DataArrayIndex ][ LOCAL.ColumnName ] = TimeFormat(ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ], "h:mmtt");
					} else {
						LOCAL.DataArray[ LOCAL.DataArrayIndex ][ LOCAL.ColumnName ] = ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ];
					}
				}
			}
			// At this point, we have an array of structure objects that
			// represent the rows in the query over the indexes that we
			// wanted to convert. If we did not want to convert a specific
			// record, return the array. If we wanted to convert a single
			// row, then return the just that STRUCTURE, not the array.
			if (ARGUMENTS.Row){
				// Return the first array item.
				return( LOCAL.DataArray[ 1 ] );
			} else {
				// Return the entire array.
				return( LOCAL.DataArray );
			}
		</cfscript>
    </cffunction>
	
	<cffunction name="deserializeWDDX" output="false" access="public" returntype="any">
		<cfargument name="wddx" required="yes" />
		<cfset var deserializedWDDX = "">
		<cfset var ret = StructNew() />
		<cfwddx action="wddx2cfml" input="#arguments.wddx#" output="deserializedWDDX" />
		<cfif IsQuery(deserializedWDDX)>
			<cfreturn QueryToStruct(deserializedWDDX) />
		<cfelse>
			<cfloop index="key" list="#StructKeyList(deserializedWDDX)#">
				<cfset ret["#LCase(key)#"] = deserializedWDDX[key] />
			</cfloop>
			<cfreturn ret />
		</cfif>
	</cffunction>
	
	<cffunction name="convertGDSDate" output="false" access="public" returntype="any">
		<cfargument name="gdsDate" required="yes" />
		<cfset var yr = "" />
		<cfset var mo = "" />
		<cfset var dy = "" />
		<cfset var dateVal = "" />
		<cfif Len(Trim(arguments.gdsDate)) eq 8>
			<cfset yr = Left(arguments.gdsDate, 4) />
			<cfset mo = Mid(arguments.gdsDate, 5, 2) />
			<cfset dy = Right(arguments.gdsDate, 2) />
			<cfset dateVal = mo & "/" & dy & "/" & yr />
			<cfreturn DateFormat(dateVal, "m/d/yyyy") />
		<cfelse>
			<cfreturn "" />
		</cfif>
	</cffunction>
	
	<cffunction name="singleQuotifyList" output="false" access="public" returntype="string">
		<cfargument name="list" type="string" required="yes" />
		<cfset var ret = "">
		<cfloop index="item" list="#arguments.list#">
			<cfset ret = ListAppend(ret, "'#item#'", ",") />
		</cfloop>
		<cfreturn ret>
	</cffunction>
	
	<cffunction name="doubleQuotifyList" output="false" access="public" returntype="string">
		<cfargument name="list" type="string" required="yes" />
		<cfset var ret = "">
		<cfloop index="item" list="#arguments.list#">
			<cfset ret = ListAppend(ret, '"#item#"', ',') />
		</cfloop>
		<cfreturn ret>
	</cffunction>
	
	<cffunction name="toNumber" output="false" access="public" returntype="string">
		<cfargument name="string" type="string" required="yes" />
		<cfset var preString = Trim(arguments.string) />
		<cfset var postString = "" />
		<cfset var i = 1 />
		<cfset var character = "" />
		<cfloop index="i" from="1" to="#Len(preString)#">
			<cfset character = Mid(preString, i, 1) />
			<cfif IsNumeric(character)>
				<cfset postString = postString & character />
			</cfif>
		</cfloop>
		<cfreturn postString />
	</cffunction>
	<!--- *************************************************
	Email related functions
	************************************************* --->
	<cffunction name="emailPassword" output="false" access="public" returntype="boolean">
		<cfargument name="email" type="string">
		<cfset var user = "">
		<cfset var recipients = "">
		<cfset var subject = "">
		<cfset var message = "">
		<cfinvoke component="assets.calgrants.com.gateway.UserGateway" method="query" returnvariable="user">
			<cfinvokeargument name="Email" value="#email#" />
		</cfinvoke>
		<cfif user.RecordCount eq 1>
			<cfset recipients = "#user.Email#">
			<cfset subject = "Your Cash for College Workshop Management System Password">
			<cfoutput>
				<cfsavecontent variable="message">
					<p>#user.First_Name# #user.Last_Name#,</p>
					<p>Your Cash for College Workshop Management System password is &quot;#user.Password#&quot;.</p>
					<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
					<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
					<p>https://www.calgrants.org/organizers</p>
					<p>Please delete this e-mail message now to protect your password.</p>
				</cfsavecontent>
			</cfoutput>
			<cfreturn sendEmail(recipients, subject, message, "HTML")>
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	
	<cffunction name="emailScholarPassword" output="false" access="public" returntype="boolean">
		<cfargument name="email" type="string">
		<cfscript>
			var scholarshipGateway = new assets.calgrants.com.gateway.ScholarshipGateway();
			var scholar = scholarshipGateway.query(Email = arguments.Email, Deleted = 0);
			var recipients = "";
			var subject = "";
			var message = "";
		</cfscript>
		<cfif scholar.RecordCount eq 1 and scholar.Deleted eq 0>
			<cfset recipients = scholar.Email>
			<cfset subject = "Your Cash for College Scholarship Password">
			<cfoutput>
				<cfsavecontent variable="message">
					<p>#scholar.First_Name# #scholar.Last_Name#,</p>
					<p>Your Cash for College Scholarship password is &quot;#scholar.Password#&quot;.</p>
					<p><a href="https://www.calgrants.org/cashforcollegescholarship">Login to your account</a></p>
					<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
					<p>https://www.calgrants.org/cashforcollegescholarship</p>
					<p>Please delete this e-mail message now to protect your password.</p>
				</cfsavecontent>
			</cfoutput>
			<cfreturn sendEmail(recipients, subject, message, "HTML")>
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>

	<cffunction name="emailWorkshopApproval" output="false" access="public" returntype="boolean">
		<cfargument name="workshopID" type="numeric">
		<cfset var workshop = "">
		<cfset var organization = "">
		<cfset var site = "">
		<cfset var organizer = "">
		<cfset var manager = "">
		<cfset var recipients = "">
		<cfset var subject = "">
		<cfset var message = "">
		<cfinvoke component="assets.calgrants.com.gateway.WorkshopGateway" method="get" returnvariable="workshop">
			<cfinvokeargument name="Workshop_ID" value="#workshop_ID#" />
		</cfinvoke>
		<cfinvoke component="assets.calgrants.com.gateway.OrganizationGateway" method="get" returnvariable="organization">
			<cfinvokeargument name="Organization_ID" value="#workshop.getOrganization_ID()#" />
		</cfinvoke>
		<cfinvoke component="assets.calgrants.com.gateway.SiteGateway" method="get" returnvariable="site">
			<cfinvokeargument name="Site_ID" value="#workshop.getSite_ID()#" />
		</cfinvoke>
		<cfinvoke component="assets.calgrants.com.gateway.UserGateway" method="get" returnvariable="organizer">
			<cfinvokeargument name="User_ID" value="#workshop.getOrganizer_ID()#" />
		</cfinvoke>
		<cfinvoke component="assets.calgrants.com.gateway.UserGateway" method="get" returnvariable="manager">
			<cfinvokeargument name="User_ID" value="#workshop.getManager_ID()#" />
		</cfinvoke>
		<cfif organizer.getEmail() neq manager.getEmail()>
			<cfset recipients = "#organizer.getEmail()#,#manager.getEmail()#">
		<cfelse>
			<cfset recipients = "#organizer.getEmail()#">
		</cfif>
		<cfset subject = "Your Cash for College workshop has been approved">
		<cfoutput>
			<cfsavecontent variable="message">
				<p>Your Cash for College workshop for #organization.getOrganization()# on #DateFormat(workshop.getWorkshop_Date(), 'dddd, mmmm d, yyyy')# from #TimeFormat(workshop.getStart_Time(), 'h:mm tt')# to #TimeFormat(workshop.getEnd_Time(), 'h:mm tt')# at #site.getSite()# has been approved.</p>
				<p>This message was sent to the workshop Organizer (#organizer.getFirst_Name()# #organizer.getLast_Name()#, #organizer.getEmail()#) and Site Contact (#manager.getFirst_Name()# #manager.getLast_Name()#, #manager.getEmail()#).</p>
				<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
				<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
				<p>https://www.calgrants.org/organizers</p>
				<p>If you have forgotten your password, click the &quot;Forgot your password?&quot; link on the login page.</p>
			</cfsavecontent>
		</cfoutput>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>
	
	<cffunction name="emailScholarshipClaimed" output="false" access="public" returntype="boolean">
		<cfargument name="Scholarship_ID" type="string">
		<cfset var scholarship = "">
		<cfset var recipients = "">
		<cfset var subject = "">
		<cfset var message = "">
		<cfinvoke component="assets.calgrants.com.gateway.ScholarshipGateway" method="get" returnvariable="scholarship">
			<cfinvokeargument name="Scholarship_ID" value="#Scholarship_ID#" />
		</cfinvoke>
		<cfif scholarship.getScholarship_ID() gt 0>
			<cfset recipients = scholarship.getEmail()>
			<!--- <cfset recipients = "DCostopo@csac.ca.gov"> --->
			<cfset subject = "You have successfully claimed your Cash for College scholarship">
			<cfoutput>
				<cfsavecontent variable="message">
					<p>#scholarship.getFirst_Name()# #scholarship.getLast_Name()#,</p>
					
					<cfif scholarship.getScholarship_Type() eq "Performance Based Scholarship">
						
						<p>Thank you for claiming your Cash for College Performance Based Scholarship. Remember to scan and email, or fax, or mail your Fall #Year(Now())# class schedule within 2 weeks of enrolling at your college so that your scholarship claim can be reviewed and processed as quickly as possible.</p>
						
						<p>If you choose to have your scholarship payments sent to you via Electronic Funds Transfer (EFT), and you did not provide banking information when you claimed, you must come back to the site below to provide your banking information. No EFT payment can be issued until we have your banking information.</p>
						
						<p>You can log into your personalized website at <a href="http://www.cashforcollegescholars.com" target="_blank">http://www.cashforcollegescholars.com</a> to get detailed information on your scholarship. Be sure to check your email at #scholarship.getEmail()# often.</p>
						
						<p>Congratulations, and keep taking the actions to make your dream education happen!</p>

						<!--- <p>Thank you for claiming your Cash for College Performance Based Scholarship. Remember to fax or mail your Fall #Year(Now())# class schedule within 2 weeks of enrolling so that your scholarship claim can be reviewed and processed as quickly as possible.</p>
						
						<cfif DateFormat(Now()) lt DateFormat("5/29/2013")>
							<p>If you haven't already registered for your online scholarship orientation on Wednesday, May 29 at 4 PM. It's easy. Visit <a href="https://cashforcollege.clickwebinar.com/Performance_Based_Scholarship/register" target="_blank">https://cashforcollege.clickwebinar.com/Performance_Based_Scholarship/register</a>.</p>
						</cfif>
											
						<p>You can log into your personalized website at <a href="http://www.cashforcollegescholars.com">http://www.cashforcollegescholars.com</a> to get detailed information on your scholarship.  Be sure to check your email at #scholarship.getEmail()# often.</p> --->
					
						<p>Congratulations, and keep taking the actions to make your dream education happen!</p>
					
					<cfelseif scholarship.getScholarship_Type() eq "CCFC">
					
						<p>Thank you for claiming your Cash for College Scholarship. Remember to fax or mail your Fall #Year(Now())# class schedule within 2 weeks of enrolling so that your scholarship claim can be reviewed and processed as quickly as possible. If you have questions check out our <a href="http://www.calgrants.org/documents/2013_Cash_for_College_Scholarship_FAQs.pdf" target="_blank">Scholarship FAQs</a>. If you need additional help, please e-mail our scholarship office detailing your question or concern at cashforcollege@lachamber.com. Please be sure to include your Scholarship Claim ID, name and Date of Birth in your e-mail.</p>
					
						<p>All future communication about your scholarship will be delivered to #scholarship.getEmail()#, so be sure to check it often.</p>
					
						<p>Congratulations, and keep taking the actions to make your dream education happen!</p>
					
					</cfif>
				</cfsavecontent>
			</cfoutput>
			<cfreturn sendEmail(recipients, subject, message, "HTML")>
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	
	<cffunction name="emailUserApproval" output="false" access="public" returntype="boolean">
		<cfargument name="userID" type="numeric" />
		<cfset var user = "" />
		<cfset var organization = "" />
		<cfset var recipients = "" />
		<cfset var subject = "" />
		<cfset var message = "" />
		<cfinvoke component="assets.calgrants.com.gateway.UserGateway" method="get" returnvariable="user">
			<cfinvokeargument name="User_ID" value="#userID#" />
		</cfinvoke>
		<cfinvoke component="assets.calgrants.com.gateway.OrganizationGateway" method="get" returnvariable="organization">
			<cfinvokeargument name="Organization_ID" value="#user.getOrganization_ID()#" />
		</cfinvoke>
		<cfset recipients = user.getEmail() />
		<cfset subject = "Your Cash for College account has been approved" />
		<cfoutput>
			<cfsavecontent variable="message">
				<p>#user.getFirst_Name()# #user.getLast_Name()#,</p>
				<p>Your Cash for College Workshop Management System (WMS) account for #organization.getOrganization()# has been approved.</p>
				<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
				<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
				<p>https://www.calgrants.org/organizers</p>
				<p>If you have forgotten your password, click the &quot;Forgot your password?&quot; link on the login page.</p>
			</cfsavecontent>
		</cfoutput>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>
	
	<cffunction name="emailNewUserNotification" output="false" access="public" returntype="boolean">
		<cfargument name="User_ID" type="numeric" />
		<cfscript>
			var user = "";
			var organization = "";
			var recipients = "";
			var subject = "New Cash for College account registered for your organization";
			var message = "";
			var userGateway = new assets.calgrants.com.gateway.UserGateway();
			var organizationGateway = new assets.calgrants.com.gateway.OrganizationGateway();
			user = userGateway.get(User_ID = arguments.User_ID);	
			organization = organizationGateway.get(Organization_ID = user.getOrganization_ID());
			organizers = userGateway.query(Organization_ID = user.getOrganization_ID(), Approved = 1, Role = "Organizer");
			recipients = ValueList(organizers.Email);
		</cfscript>
		<cfoutput>
			<cfsavecontent variable="message">
				<p>You are receiving this message because you are an Organizer for #organization.getOrganization()# in the Cash for College Workshop Management System (WMS).</p>
				<p>A new account for #user.getFirst_Name()# #user.getLast_Name()# has been created using the &quot;Create an account&quot; link on the login page of the WMS.  #user.getFirst_Name()# #user.getLast_Name()# can login to the WMS and modify records associated with #organization.getOrganization()#.</p>
				<p>To modify the new user account login to the WMS and select &quot;Users&quot; in the menu under your name (upper-right corner of window).</p>
				<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
				<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
				<p>https://www.calgrants.org/organizers</p>
				<p>If you have forgotten your password, click the &quot;Forgot your password?&quot; link on the login page.</p>
			</cfsavecontent>
		</cfoutput>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>
	
	<cffunction name="emailNewOrderNotification" output="false" access="public" returntype="boolean">
		<cfargument name="CSAC_Order_ID" type="numeric" />
		<cfargument name="Order_ID" type="numeric" required="no" default="0" hint="Pre 2013-14" />
		<cfscript>
			var csacOrderGateway = new assets.calgrants.com.gateway.CSACOrderGateway();
			var csacOrder = csacOrderGateway.get(CSAC_Order_ID = arguments.CSAC_Order_ID);
			var recipients = csacOrder.getEmail();
			var subject = "New Cash for College Order ###csacOrder.getCSAC_Order_ID()#";
			var message = "";
		</cfscript>
		<cfsavecontent variable="message">
			<p>Thank you for your Cash for College materials order! This message confirms order ###csacOrder.getCSAC_Order_ID()# was successfully submitted and received.</p>
			<p>You will receive another e-mail message, including delivery tracking numbers, when the order ships. The order can be reviewed in your organization account in the menu under &quot;Prepare&quot; then &quot;Order Materials&quot;.</p>
			<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
			<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
			<p>https://www.calgrants.org/organizers</p>
			<p>If you have forgotten your password, click the &quot;Forgot your password?&quot; link on the login page.</p>
			<p>If you have questions about your order, send an email to <a href="mailto:cashforcollege@csac.ca.gov">cashforcollege@csac.ca.gov</a>.</p>
		</cfsavecontent>
		<cfreturn sendEmail(recipients, subject, message, "HTML") />
	</cffunction>
	
	<cffunction name="emailApprovedOrderNotification" output="false" access="public" returntype="boolean">
		<cfargument name="CSAC_Order_ID" type="numeric" />
		<cfargument name="Order_ID" type="numeric" required="no" default="0" hint="Pre 2013-14" />
		<cfscript>
			var csacOrderGateway = new assets.calgrants.com.gateway.CSACOrderGateway();
			var csacOrder = csacOrderGateway.get(CSAC_Order_ID = arguments.CSAC_Order_ID);
			var recipients = csacOrder.getEmail();
			var subject = "Cash for College Order ###csacOrder.getCSAC_Order_ID()# Approved";
			var message = "";
		</cfscript>
		<cfsavecontent variable="message">
			<p>Your Cash for College materials order has been approved.</p>
			<p>You will receive another e-mail message, including delivery tracking numbers, when the order ships. The order can be reviewed in your organization account in the menu under &quot;Prepare&quot; then &quot;Order Materials&quot;.</p>
			<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
			<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
			<p>https://www.calgrants.org/organizers</p>
			<p>If you have forgotten your password, click the &quot;Forgot your password?&quot; link on the login page.</p>
			<p>If you have questions about your order, send an email to <a href="mailto:cashforcollege@csac.ca.gov">cashforcollege@csac.ca.gov</a>.</p>
		</cfsavecontent>
		<cfreturn sendEmail(recipients, subject, message, "HTML") />
	</cffunction>
	
	<cffunction name="emailShippedOrderNotification" output="false" access="public" returntype="boolean">
		<cfargument name="CSAC_Order_ID" type="numeric" />
		<cfargument name="Order_ID" type="numeric" required="no" default="0" hint="Pre 2013-14" />
		<cfscript>
			var csacOrderGateway = new assets.calgrants.com.gateway.CSACOrderGateway();
			var csacOrder = csacOrderGateway.get(CSAC_Order_ID = arguments.CSAC_Order_ID);
			var orderGateway = new assets.csac.com.gateway.OrderGateway();
			var order = orderGateway.get(WMS_Order_ID = csacOrder.getCSAC_Order_ID());
			var orderProductsGateway = new assets.csac.com.gateway.OrderProductsGateway();
			var orderProducts = orderProductsGateway.query(Order_ID = order.getOrder_ID());
			var recipients = csacOrder.getEmail();
			var subject = "Cash for College Order ###csacOrder.getCSAC_Order_ID()# Shipped";
			var message = "";
		</cfscript>
		<cfquery name="uniqueTrackingCodes" dbtype="query">
			select DISTINCT Tracking_Code from orderProducts
		</cfquery>
		<cfsavecontent variable="message">
			<p>Your Cash for College materials order has shipped.</p>
			<p>The tracking numbers for your order are:</p>
			<ul>
				<cfloop query="uniqueTrackingCodes">
					<li><a href="http://www.fedex.com/Tracking?action=track&tracknumbers=#Tracking_Code#">#Tracking_Code#</a></li>
				</cfloop>
			</ul>
			<p>The order can be reviewed in your organization account in the menu under &quot;Prepare&quot; then &quot;Order Materials&quot;.</p>
			<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
			<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
			<p>https://www.calgrants.org/organizers</p>
			<p>If you have forgotten your password, click the &quot;Forgot your password?&quot; link on the login page.</p>
			<p>If you have questions about your order, send an email to <a href="mailto:cashforcollege@csac.ca.gov">cashforcollege@csac.ca.gov</a>.</p>
		</cfsavecontent>
		<cfreturn sendEmail(recipients, subject, message, "HTML") />
	</cffunction>

	<cffunction name="emailPaymentDocumented" output="false" access="public" returntype="boolean">
		<cfargument name="Payment_ID" type="numeric">
		<cfset var docType = "">
		<cfset var recipients = "" />
		<cfset var subject = "">
		<cfset var message = "">
		<cfscript>
			var paymentGateway = new assets.calgrants.com.gateway.PaymentGateway();
			var payment = paymentGateway.get(Payment_ID = arguments.Payment_ID);
			var scholarshipGateway = new assets.calgrants.com.gateway.ScholarshipGateway();
			var scholarship = scholarshipGateway.get(Scholarship_ID = payment.getScholarship_ID());
			var paymentScheduleGateway = new assets.calgrants.com.gateway.PaymentScheduleGateway();
			var paymentSchedule = paymentScheduleGateway.get(Payment_Schedule_ID = payment.getPayment_Schedule_ID());
		</cfscript>
		<cfif paymentSchedule.getVerification_Type() eq "Enrollment">
			<cfset docType = "class schedule" />
		<cfelseif paymentSchedule.getVerification_Type() eq "Performance">
			<cfset docType = "transcript" />
		</cfif>
		<cfset recipients = scholarship.getEmail() />
		<cfset subject = "Your #docType#(s) have been received by Cash for College">
		<cfoutput>	
			<cfsavecontent variable="message">
<p>#scholarship.getFirst_Name()# #scholarship.getLast_Name()#,</p>
<p>This e-mail message is to confirm that your #docType#(s) for #paymentSchedule.getTerm()# have been received by the Cash for College scholarship office. Your class schedule is being reviewed and is pending approval. If there is a problem with your class schedule, we will contact you. Otherwise, you will receive a confirmation e-mail when your scholarship payment has been approved.</p>
<p>You can review your scholarship details and update your contact and college information at the <a href="http://www.calgrants.org/cashforcollegescholarship">Cash for College Scholarship Site</a>.  If you have any questions please contact us at <a href="mailto:#request.payment.email#">#request.payment.email#</a></p>
			</cfsavecontent>
		</cfoutput>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>
	
	<cffunction name="emailPaymentApproved" output="false" access="public" returntype="boolean">
		<cfargument name="Payment_ID" type="numeric">
		<cfset var recipients = "" />
		<cfset var subject = "">
		<cfset var message = "">
		<cfscript>
			var paymentGateway = new assets.calgrants.com.gateway.PaymentGateway();
			var payment = paymentGateway.get(Payment_ID = arguments.Payment_ID);
			var scholarshipGateway = new assets.calgrants.com.gateway.ScholarshipGateway();
			var scholarship = scholarshipGateway.get(Scholarship_ID = payment.getScholarship_ID());
			var paymentScheduleGateway = new assets.calgrants.com.gateway.PaymentScheduleGateway();
			var paymentSchedule = paymentScheduleGateway.get(Payment_Schedule_ID = payment.getPayment_Schedule_ID());
		</cfscript>
		<cfset recipients = scholarship.getEmail() />
		<cfset subject = "Your scholarship payment has been approved by Cash for College">		
		<cfoutput>
			<cfsavecontent variable="message">
				<p>#scholarship.getFirst_Name()# #scholarship.getLast_Name()#,</p>
				<p>This e-mail message is to confirm that your scholarship payment for #paymentSchedule.getTerm()# has been approved by the Cash for College scholarship office. Your scholarship payment will be processed within the next few weeks and you will receive a confirmation e-mail when your payment has been made.</p>
				<p>Remember, you can also check the status of your scholarship payment by logging into your scholarship account at the <a href="https://www.calgrants.org/cashforcollegescholarship">Cash for College Scholarship Site</a>.</p>
			</cfsavecontent>
		</cfoutput>
		<cfreturn sendEmail(recipients, subject, message, "html")>
	</cffunction>
	
	<cffunction name="emailPaymentPaid" output="false" access="public" returntype="boolean">
		<cfargument name="Payment_ID" type="numeric">
		<cfset var recipients = "" />
		<cfset var subject = "">
		<cfset var message = "">
		<cfscript>
			var paymentGateway = new assets.calgrants.com.gateway.PaymentGateway();
			var payment = paymentGateway.get(Payment_ID = arguments.Payment_ID);
			var scholarshipGateway = new assets.calgrants.com.gateway.ScholarshipGateway();
			var scholarship = scholarshipGateway.get(Scholarship_ID = payment.getScholarship_ID());
			var paymentScheduleGateway = new assets.calgrants.com.gateway.PaymentScheduleGateway();
			var paymentSchedule = paymentScheduleGateway.get(Payment_Schedule_ID = payment.getPayment_Schedule_ID());
			var institutionGateway = new assets.calgrants.com.gateway.InstitutionGateway();
			var institution = institutionGateway.get(Institution_ID = payment.getInstitution1_ID());
		</cfscript>
		<cfset recipients = scholarship.getEmail() />
		<cfset subject = "Your scholarship payment has been sent by Cash for College">			
		<cfoutput>
			<cfsavecontent variable="message">
<p>#scholarship.getFirst_Name()# #scholarship.getLast_Name()#,</p>
<p>This e-mail message is to inform you that your scholarship payment for #paymentSchedule.getTerm()# has been paid by the Cash for College scholarship office.
<cfif payment.getPayment_Type() eq "Check">
A check has been mailed to the financial aid office at #institution.getInstitution()# and should be available within a week of the date of this e-mail message.
<cfelseif payment.getPayment_Type() eq "EFT">
Your payment has been deposited into your bank account using the information you provided at the <a href="http://www.calgrants.org/cashforcollegescholarship">Cash for College Scholarship Site</a>.  
</cfif>
</p> 
<p>You can update your contact, institution and banking information at <a href="http://www.calgrants.org/cashforcollegescholarship">Cash for College Scholarship Site</a>.  If you have any questions please contact us at <a href="mailto:#request.payment.email#">#request.payment.email#</a></p>
			</cfsavecontent>
		</cfoutput>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>
	
	<cffunction name="shareHowDidWeDoReport" output="false" access="public" returntype="any">
		<cfargument name="shareMessage" type="string" required="yes">
		<cfargument name="shareEmail" type="string" required="yes">
		<cfargument name="rollupLevel" type="string" required="yes">
		<cfargument name="ID" type="numeric" required="yes">
		<cfargument name="User_ID" type="numeric" required="yes">
		<cfargument name="Program_Year" type="string" required="yes" />
		<cfset var userGateway = new assets.calgrants.com.gateway.UserGateway() />
		<cfset var user = userGateway.get(User_ID = arguments.User_ID)>
		<cfset var recipients = arguments.shareEmail>
		<cfset var subject = "Cash for College How Did We Do Report">
		<cfset var message = "">
		<cfset var reportLink = "http://www.calgrants.org/assets/calgrants/ui/printable/howDidWeDo.cfm?" & arguments.rollupLevel & "_ID=" & arguments.ID & "&Program_Year=" & arguments.Program_Year />
		<cftry>
			<cfoutput>
				<cfsavecontent variable="message">
					<p>#user.getFirst_Name()# #user.getLast_Name()# has shared their Cash for College How Did We Do report with you.</p>
					<p><a href="#reportLink#">View the report</a></p>
				</cfsavecontent>
			</cfoutput>
			<cfcatch type="any">
				<cfreturn cfcatch>
			</cfcatch>
		</cftry>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>

	<cffunction name="sendEmail" output="false" access="public" returntype="any">
		<cfargument name="recipients" type="string" required="yes" hint="One email or a comma-separated list of emails">
		<cfargument name="subject" type="string" required="yes">
		<cfargument name="message" type="string" required="yes">
		<cfargument name="emailType" type="string" required="no" default="HTML">
		<cfset local.result = true />
		<cfset local.recipients = arguments.recipients />
		<cfset local.from = "cashforcollege@csac.ca.gov" />
		<cfset local.replyTo = local.from />
		<cfset local.bcc = "cash4collegeca@gmail.com" />
		<cfif StructKeyExists(request, "developer") and StructKeyExists(request, "personality")>
			<cfset local.bcc = "#request.developer.email#,#request.personality.bcc#" />
			<cfset local.from = request.personality.email />
			<cfset local.replyTo = request.personality.email />
			<cfif request.mode eq "dev">
				<cfset local.recipients = request.developer.email>
				<cfset local.bcc = "">
			</cfif>
		</cfif>
		<cftry>
			<cfloop index="recipient" list="#local.recipients#">
				<cfmail to="#recipient#" bcc="#local.bcc#" from="#local.from#" replyto="#local.replyTo#" subject="#arguments.subject#" type="#arguments.emailType#">
					<cfif arguments.emailType eq "HTML">
						<img src="http://www.calgrants.org/common/images/Cash_for_College_logo.gif" alt="Cash for College" style="margin-bottom: 20px;"><br>
						#arguments.message#
					<cfelseif arguments.emailType eq "Plain Text">
						#arguments.message#
					</cfif>
				</cfmail>
			</cfloop>
			<cfcatch type="any">
				<cfset local.result = false />
			</cfcatch>
		</cftry>
		<cfreturn local.result>
	</cffunction>
	
	<cffunction name="saveMailChimpList" output="true" access="public" returntype="boolean">
		<cfargument name="action" type="string" required="yes" hint="The value for action is either append or replace">
		<cfargument name="listID" type="string" required="yes" hint="MailChimp List ID">
		<cfargument name="recipients" type="array" required="yes">
		<!--- <cfobject component="globals.cfc.mailchimp" name="mailchimp">
		<cfset mailchimp.init(apiKey = request.MAILCHIMP_API_KEY)> --->
		<cfset mailChimp = createObject("component","globals.cfc.mailChimp").init(request.MAILCHIMP_API_KEY)>
		<!--- if action is replace, delete all members from selected list --->
		<cfif arguments.action eq "replace">
			<cfset var args = StructNew()>
			<cfset args["id"] = arguments.listID>
			<cfset args["limit"] = 15000>
			<cfset list = mailchimp.do("listMembers", args)>
			<cfif structKeyExists(list, "data")>
				<cfif arrayLen(list.data)>
					<cfset args = StructNew()>
					<cfset args["id"] = arguments.listID>
					<cfset args["delete_member"] = true>
					<cfset args["send_goodbye"] = false>
					<cfset args["send_notify"] = false>
					
					<cfloop index="i" from="1" to="#arrayLen(list.data)#">
						<cfset args["emails[#i#]"] = list.data[i].email>
					</cfloop>
					<cfset mailchimp.do("listBatchUnsubscribe", args)>
				</cfif>
			</cfif>
		</cfif>
		<!--- add recipients to list --->
		<cfset args = StructNew()>
		<cfset args["id"] = arguments.listID>
		<cfset args["double_optin"] = false>
		<cfset args["update_existing"] = true>
		<cfloop from="1" to="#ArrayLen(arguments.recipients)#" index="i">
			<cfset args["batch[#i#][EMAIL]"] = arguments.recipients[i].email>
			<cfset args["batch[#i#][FNAME]"] = arguments.recipients[i].firstName>
			<cfset args["batch[#i#][LNAME]"] = arguments.recipients[i].lastName>
			<cfset args["batch[#i#][ORG]"] = arguments.recipients[i].organization>
			<cfset args["batch[#i#][LISTTYPE]"] = arguments.recipients[i].listType>			
			<cfif StructKeyExists(arguments.recipients[i], "claimID")>
				<cfset args["batch[#i#][CLAIMID]"] = arguments.recipients[i].claimID>
			<cfelse>
				<cfset args["batch[#i#][CLAIMID]"] = "">
			</cfif>
		</cfloop>
		<cfset args["batch[#i+1#][EMAIL]"] = "ken@syntric.com">
		<cfset args["batch[#i+1#][FNAME]"] = "Ken">
		<cfset args["batch[#i+1#][LNAME]"] = "Hobbs">
		<cfset args["batch[#i+1#][ORG]"] = "Syntric">
		<cfset args["batch[#i+1#][LISTTYPE]"] = "Test E-mail Account">
		<cfset args["batch[#i+1#][CLAIMID]"] = "">
		<cfset args["batch[#i+2#][EMAIL]"] = "dcostopo@csac.ca.gov">
		<cfset args["batch[#i+2#][FNAME]"] = "Despina">
		<cfset args["batch[#i+2#][LNAME]"] = "Costopoulos">
		<cfset args["batch[#i+2#][ORG]"] = "Cash for College">
		<cfset args["batch[#i+2#][LISTTYPE]"] = "Test E-mail Account">
		<cfset args["batch[#i+2#][CLAIMID]"] = "">
		<cfset mailchimp.do("listBatchSubscribe", args)>
		<!--- <cfset args = structNew()>
		<cfset args["type"] = "regular">
		<cfset args["options[list_id]"] = listId>
		<cfset args["options[subject]"] = sbj>
		<cfset args["options[from_email]"] = request.adminEmail>
		<cfset args["options[from_name]"] = "Cash For College">
		<cfset args["options[auto_footer]"] = false>
		<cfset args["options[timewarp]"] = false>
		<cfset args["content[html]"] = msg>
		<cfset var campaignId = mailchimp.do("campaignCreate", args)>
		<cfset args = structNew()>
		<cfset args["cid"] = campaignId>
		<cfset var ret = mailchimp.do("campaignSendNow", args)>
		<cfreturn ret> --->
		<cfreturn true>
	</cffunction>
	
	<cffunction name="sendMassEmail" output="true" access="public" returntype="boolean">
		<cfargument name="recipientsArray" type="array" required="yes">
		<cfargument name="subject" type="string" required="yes">
		<cfargument name="message" type="string" required="yes">
		<cfset var emailBody = "">
		<cfsavecontent variable="emailBody">
			<style type="text/css">
				<cfinclude template="/applications/admin/common/workshops.css">
				table {display: none; visibility: hidden;}
				table.visible {display: block; visibility: visible;}
				center {text-align: left;}
				a {text-decoration: underline;}
			</style>
			<table width="750" cellpadding="8" style="border: solid 1px silver;" class="visible">
				<tr>
					<td><img src="http://www.calgrants.org/applications/admin/common/images/smlogo.gif" height="51" width="200" border="0" alt="Cash for College logo" style="margin-bottom: 10px;"></td>
				</tr>
				<tr>
					<td>#arguments.message#</td>
				</tr>
			</table>
		</cfsavecontent>
		<cfset sbj = ReplaceNoCase(arguments.subject, "%firstName%", "*|FNAME|*", "all")>
		<cfset sbj = ReplaceNoCase(sbj, "%lastName%", "*|LNAME|*", "all")>
		<cfset sbj = ReplaceNoCase(sbj, "%organization%", "*|ORG|*", "all")>
		<cfset sbj = ReplaceNoCase(sbj, "%email%", "*|EMAIL|*", "all")>
		<cfset sbj = ReplaceNoCase(sbj, "%claimID%", "*|CLAIMID|*", "all")>
		<cfset msg = ReplaceNoCase(emailBody, "%firstName%", "*|FNAME|*", "all")>
		<cfset msg = ReplaceNoCase(msg, "%lastName%", "*|LNAME|*", "all")>
		<cfset msg = ReplaceNoCase(msg, "%organization%", "*|ORG|*", "all")>
		<cfset msg = ReplaceNoCase(msg, "%email%", "*|EMAIL|*", "all")>
		<cfset msg = ReplaceNoCase(msg, "%claimID%", "*|CLAIMID|*", "all")>
		<cfobject component="globals.cfc.mailchimp" name="mailchimp">
		<cfset mailchimp.init(apiKey = request.MAILCHIMP_API_KEY)>
		<cfset var listId = request.MAILCHIMP_LISTID>
		<cfset var args = structNew()>
		<cfset args["id"] = listId>
		<cfset args["limit"] = 15000>
		<cfset list = mailchimp.do("listMembers", args)>
		<cfif structKeyExists(list, "data")>
			<cfif arrayLen(list.data)>
				<cfset args = structNew()>
				<cfset args["id"] = listId>
				<cfset args["delete_member"] = true>
				<cfset args["send_goodbye"] = false>
				<cfset args["send_notify"] = false>
				<cfloop index="i" from="1" to="#arrayLen(list.data)#">
					<cfset args["emails[#i#]"] = list.data[i].email>
				</cfloop>
				<cfset mailchimp.do("listBatchUnsubscribe", args)>
			</cfif>
		</cfif>
		<cfset args = structNew()>
		<cfset args["id"] = listId>
		<cfset args["double_optin"] = false>
		<cfset args["update_existing"] = true>
		<cfloop from="1" to="#arrayLen(arguments.recipientsArray)#" index="i">
			<cfset args["batch[#i#][EMAIL]"] = arguments.recipientsArray[i].email>
			<cfset args["batch[#i#][FNAME]"] = arguments.recipientsArray[i].firstName>
			<cfset args["batch[#i#][LNAME]"] = arguments.recipientsArray[i].lastName>
			<cfset args["batch[#i#][ORG]"] = arguments.recipientsArray[i].organization>
			<cfif StructKeyExists(arguments.recipientsArray[i], "claimID")>
				<cfset args["batch[#i#][CLAIMID]"] = arguments.recipientsArray[i].claimID>
			</cfif>
		</cfloop>
		<cfset args["batch[#i+1#][EMAIL]"] = "ken@syntric.com">
		<cfset args["batch[#i+1#][FNAME]"] = "Ken">
		<cfset args["batch[#i+1#][LNAME]"] = "Hobbs">
		<cfset args["batch[#i+1#][ORG]"] = "Syntric">
		<cfset args["batch[#i+2#][EMAIL]"] = "DCostopo@csac.ca.gov">
		<cfset args["batch[#i+2#][FNAME]"] = "Despina">
		<cfset args["batch[#i+2#][LNAME]"] = "Costopoulos">
		<cfset args["batch[#i+2#][ORG]"] = "California Cash for College">
		<cfset mailchimp.do("listBatchSubscribe", args)>
		<cfset args = structNew()>
		<cfset args["type"] = "regular">
		<cfset args["options[list_id]"] = listId>
		<cfset args["options[subject]"] = sbj>
		<cfset args["options[from_email]"] = request.adminEmail>
		<cfset args["options[from_name]"] = "Cash For College">
		<cfset args["options[auto_footer]"] = false>
		<cfset args["options[timewarp]"] = false>
		<cfset args["content[html]"] = msg>
		<cfset var campaignId = mailchimp.do("campaignCreate", args)>
		<cfset args = structNew()>
		<cfset args["cid"] = campaignId>
		<cfset var ret = mailchimp.do("campaignSendNow", args)>
		<cfreturn ret>
	</cffunction>
	<!--- *************************************************
	Defaults, lists, calcs, algorithms, etc.
	************************************************* --->
	<cffunction name="getDatasource" output="false" access="public" returntype="string">
		<!--- <cfif IsDefined("request.datasource")>
			<cfreturn request.datasource />
		<cfelseif IsDefined("application.datasource")>
			<cfreturn application.datasource />
		<cfelse>
			<cfif cgi.HTTP_HOST eq "calgrants.org" OR cgi.HTTP_HOST eq "www.calgrants.org">
				<cfreturn "WMS" />
			<cfelse>
				<cfreturn "WMS_Dev" />
			</cfif>
		</cfif> --->
		<cfreturn "WMS" />
	</cffunction>
	
	<cffunction name="getOrderDatasource" output="false" access="public" returntype="string">
		<!--- <cfif IsDefined("request.datasource")>
			<cfreturn request.datasource />
		<cfelseif IsDefined("application.datasource")>
			<cfreturn application.datasource />
		<cfelse>
			<cfif cgi.HTTP_HOST eq "calgrants">
				<cfreturn "CSAC_Dev" />
			<cfelse>
				<cfreturn "CSAC" />
			</cfif>
		</cfif> --->
		<cfreturn "CSAC" />
	</cffunction>
	
	<cffunction name="getValidator" output="false" access="public" returntype="any">
		<cfset var validator = "" />
		<cfif IsDefined("request.validator")>
			<cfreturn request.validator />
		<cfelseif IsDefined("application.validator")>
			<cfreturn application.validator />
		<cfelse>
			<cfreturn new validateThis.ValidateThis({extraRuleValidatorComponentPaths = "#GetDirectoryFromPath(ExpandPath('*'))#\validators\", definitionPath = "#GetDirectoryFromPath(ExpandPath('*'))#\bo\"}) />
		</cfif>
	</cffunction>
	
	<cffunction name="getCurrentProgramYear" output="false" access="public" returntype="string">
		<!--- program year rolls over on September 1, format is "2012-2013", etc. --->
		<cfscript>
			if (Month(Now()) lte 8) {
				return Year(Now()) - 1 & "-" & Year(Now());
			} else {
				return Year(Now()) & "-" & Year(Now()) + 1;
			}
		</cfscript>
	</cffunction>
	
	<cffunction name="getCurrentCohort" output="false" access="public" returntype="numeric">
		<!--- Returns the largest Cohort value from Scholarship --->
		<cfscript>
			var storedProcedureGateway = new assets.calgrants.com.gateway.StoredProcedureGateway();
			var result = storedProcedureGateway.call("getCurrentCohort");
			return result.currentCohort;
		</cfscript>
	</cffunction>
	
	<cffunction name="getDateNow" output="false" access="public" returntype="string">
		<!--- returns today's date as a yyyymmdd formatted string --->
		<cfscript>
			var monthNow = IIf(Month(Now()) lt 10, "0" & Month(Now()), Month(Now()));
			var dayNow = IIf(Day(Now()) lt 10, "0" & Day(Now()), Day(Now()));
			return Year(Now()) & monthNow & dayNow;
		</cfscript>
	</cffunction>
	
	<cffunction name="getPriorProgramYear" output="false" access="public" returntype="string">
		<cfscript>
			var currentProgramYear = getCurrentProgramYear();
			var endYear = ListFirst(currentProgramYear, "-");
			var startYear = endYear - 1;
			return startYear & "-" & endYear;
		</cfscript>
	</cffunction>
	
	<cffunction name="getPBSScholarshipTypes" output="false" access="public" returntype="any">
		<cfargument name="quoted" type="boolean" default="false" />
		<cfscript>
			var storedProcedureGateway = new assets.calgrants.com.gateway.StoredProcedureGateway();
			var pbsScholarshipTypes = storedProcedureGateway.call("getDistinctPBSScholarshipTypes");
			if (NOT arguments.quoted) {
				return ValueList(pbsScholarshipTypes.Scholarship_Type);
			} else {
				return QuotedValueList(pbsScholarshipTypes.Scholarship_Type);
			}
		</cfscript>
	</cffunction>
	
	<cffunction name="getNonPBSScholarshipTypes" output="false" access="public" returntype="any">
		<cfargument name="quoted" type="boolean" default="false" />
		<cfscript>
			var storedProcedureGateway = new assets.calgrants.com.gateway.StoredProcedureGateway();
			var nonPBSScholarshipTypes = storedProcedureGateway.call("getDistinctNonPBSScholarshipTypes");
			if (NOT arguments.quoted) {
				return ValueList(nonPBSScholarshipTypes.Scholarship_Type);
			} else {
				return QuotedValueList(nonPBSScholarshipTypes.Scholarship_Type);
			}
		</cfscript>
	</cffunction>
	
	<cffunction name="getOfflineReasons" output="false" access="public" returntype="array">
		<cfset var offlineReasons = ArrayNew(1)>
		<cfset offlineReasons[1] = StructNew()>
		<cfset offlineReasons[1].reason = "No computers">
		<cfset offlineReasons[1].description = "The site does not have computers">
		<cfset offlineReasons[2] = StructNew()>
		<cfset offlineReasons[2].reason = "Community site">
		<cfset offlineReasons[2].description = "The site is a community facility without available computers">
		<cfset offlineReasons[3] = StructNew()>
		<cfset offlineReasons[3].reason = "Too many students">
		<cfset offlineReasons[3].description = "Workshop will serve too many students for the available computers">
		<cfset offlineReasons[4] = StructNew()>
		<cfset offlineReasons[4].reason = "Too few volunteers">
		<cfset offlineReasons[4].description = "Not enough volunteers to support an online workshop">
		<cfset offlineReasons[5] = StructNew()>
		<cfset offlineReasons[5].reason = "Other">
		<cfset offlineReasons[5].description = "Other reason, please describe">
		<cfreturn offlineReasons>
	</cffunction>
	
	<cffunction name="getWorkshopHolidays" output="false" access="public" returntype="array">
		<cfset var workshopHolidays = ArrayNew(1)>
		<!--- <cfset workshopHolidays[1] = StructNew()>
		<cfset workshopHolidays[1]["name"] = "New Year Day">
		<cfset workshopHolidays[1]["holiday"] = CreateDate(2013, 1, 1)>
		<cfset workshopHolidays[1]["forbid"] = true>
		<cfset workshopHolidays[2] = StructNew()>
		<cfset workshopHolidays[2]["name"] = "Day after New Year Day">
		<cfset workshopHolidays[2]["holiday"] = CreateDate(2013, 1, 2)>
		<cfset workshopHolidays[2]["forbid"] = true>
		<cfset workshopHolidays[3] = StructNew()>
		<cfset workshopHolidays[3]["name"] = "Martin Luther King Jr birthday">
		<cfset workshopHolidays[3]["holiday"] = CreateDate(2013, 1, 21)>
		<cfset workshopHolidays[3]["forbid"] = true>
		<cfset workshopHolidays[4] = StructNew()>
		<cfset workshopHolidays[4]["name"] = "Chinese New Year Day">
		<cfset workshopHolidays[4]["holiday"] = CreateDate(2013, 2, 10)>
		<cfset workshopHolidays[4]["forbid"] = false>
		<cfset workshopHolidays[5] = StructNew()>
		<cfset workshopHolidays[5]["name"] = "Abraham Lincoln birthday">
		<cfset workshopHolidays[5]["holiday"] = CreateDate(2013, 2, 11)>
		<cfset workshopHolidays[5]["forbid"] = false>
		<cfset workshopHolidays[6] = StructNew()>
		<cfset workshopHolidays[6]["name"] = "Ash Wednesday">
		<cfset workshopHolidays[6]["holiday"] = CreateDate(2013, 2, 13)>
		<cfset workshopHolidays[6]["forbid"] = false>
		<cfset workshopHolidays[7] = StructNew()>
		<cfset workshopHolidays[7]["name"] = "George Washington birthday">
		<cfset workshopHolidays[7]["holiday"] = CreateDate(2013, 2, 18)>
		<cfset workshopHolidays[7]["forbid"] = true> --->
		<!--- <cfset workshopHolidays[1] = StructNew()>
		<cfset workshopHolidays[1]["name"] = "New Year Day">
		<cfset workshopHolidays[1]["holiday"] = DateFormat(CreateDate(2014, 1, 1), "m/d/yyyy")>
		<cfset workshopHolidays[1]["forbid"] = true>
		<cfset workshopHolidays[2] = StructNew()>
		<cfset workshopHolidays[2]["name"] = "Day after New Year Day">
		<cfset workshopHolidays[2]["holiday"] = DateFormat(CreateDate(2014, 1, 2), "m/d/yyyy")>
		<cfset workshopHolidays[2]["forbid"] = true>
		<cfset workshopHolidays[3] = StructNew()>
		<cfset workshopHolidays[3]["name"] = "Martin Luther King Jr birthday">
		<cfset workshopHolidays[3]["holiday"] = DateFormat(CreateDate(2014, 1, 20), "m/d/yyyy")>
		<cfset workshopHolidays[3]["forbid"] = true>
		<cfset workshopHolidays[4] = StructNew()>
		<cfset workshopHolidays[4]["name"] = "Chinese New Year Day">
		<cfset workshopHolidays[4]["holiday"] = DateFormat(CreateDate(2014, 1, 31), "m/d/yyyy")>
		<cfset workshopHolidays[4]["forbid"] = false>
		<cfset workshopHolidays[5] = StructNew()>
		<cfset workshopHolidays[5]["name"] = "Abraham Lincoln birthday">
		<cfset workshopHolidays[5]["holiday"] = DateFormat(CreateDate(2014, 2, 12), "m/d/yyyy")>
		<cfset workshopHolidays[5]["forbid"] = false>
		<cfset workshopHolidays[6] = StructNew()>
		<cfset workshopHolidays[6]["name"] = "President's Day">
		<cfset workshopHolidays[6]["holiday"] = DateFormat(CreateDate(2014, 2, 17), "m/d/yyyy")>
		<cfset workshopHolidays[6]["forbid"] = true>
		<cfset workshopHolidays[7] = StructNew()>
		<cfset workshopHolidays[7]["name"] = "Ash Wednesday">
		<cfset workshopHolidays[7]["holiday"] = DateFormat(CreateDate(2014, 3, 5), "m/d/yyyy")>
		<cfset workshopHolidays[7]["forbid"] = false>
		<cfset workshopHolidays[8] = StructNew()>
		<cfset workshopHolidays[8]["name"] = "2014-2015 Dream Act Application not available online">
		<cfset workshopHolidays[8]["holiday"] = DateFormat(CreateDate(2014, 1, 3), "m/d/yyyy")>
		<cfset workshopHolidays[8]["forbid"] = "dreamAct">
		<cfset workshopHolidays[9] = StructNew()>
		<cfset workshopHolidays[9]["name"] = "2014-2015 Dream Act Application not available online">
		<cfset workshopHolidays[9]["holiday"] = DateFormat(CreateDate(2014, 1, 4), "m/d/yyyy")>
		<cfset workshopHolidays[9]["forbid"] = "dreamAct">
		<cfset workshopHolidays[10] = StructNew()>
		<cfset workshopHolidays[10]["name"] = "2014-2015 Dream Act Application not available online">
		<cfset workshopHolidays[10]["holiday"] = DateFormat(CreateDate(2014, 1, 5), "m/d/yyyy")>
		<cfset workshopHolidays[10]["forbid"] = "dreamAct"> --->
		<cfset workshopHolidays[1] = StructNew()>
		<cfset workshopHolidays[1]["name"] = "New Year Day">
		<cfset workshopHolidays[1]["holiday"] = DateFormat(CreateDate(2015, 1, 1), "m/d/yyyy")>
		<cfset workshopHolidays[1]["forbid"] = true>
		<cfset workshopHolidays[2] = StructNew()>
		<cfset workshopHolidays[2]["name"] = "Day after New Year Day">
		<cfset workshopHolidays[2]["holiday"] = DateFormat(CreateDate(2015, 1, 2), "m/d/yyyy")>
		<cfset workshopHolidays[2]["forbid"] = true>
		<cfset workshopHolidays[3] = StructNew()>
		<cfset workshopHolidays[3]["name"] = "Martin Luther King Jr birthday">
		<cfset workshopHolidays[3]["holiday"] = DateFormat(CreateDate(2015, 1, 19), "m/d/yyyy")>
		<cfset workshopHolidays[3]["forbid"] = true>
		<cfset workshopHolidays[4] = StructNew()>
		<cfset workshopHolidays[4]["name"] = "President's Day">
		<cfset workshopHolidays[4]["holiday"] = DateFormat(CreateDate(2015, 2, 16), "m/d/yyyy")>
		<cfset workshopHolidays[4]["forbid"] = true>
		
		<cfset workshopHolidays[5] = StructNew()>
		<cfset workshopHolidays[5]["name"] = "Valentine's Day">
		<cfset workshopHolidays[5]["holiday"] = DateFormat(CreateDate(2015, 2, 14), "m/d/yyyy")>
		<cfset workshopHolidays[5]["forbid"] = false>
		<cfset workshopHolidays[6] = StructNew()>
		<cfset workshopHolidays[6]["name"] = "Ash Wednesday">
		<cfset workshopHolidays[6]["holiday"] = DateFormat(CreateDate(2015, 2, 18), "m/d/yyyy")>
		<cfset workshopHolidays[6]["forbid"] = false>
		<cfset workshopHolidays[7] = StructNew()>
		<cfset workshopHolidays[7]["name"] = "Chinese New Year">
		<cfset workshopHolidays[7]["holiday"] = DateFormat(CreateDate(2015, 2, 19), "m/d/yyyy")>
		<cfset workshopHolidays[7]["forbid"] = false>
		
		<cfset workshopHolidays[8] = StructNew()>
		<cfset workshopHolidays[8]["name"] = "2015-2016 Dream Act Application not available online">
		<cfset workshopHolidays[8]["holiday"] = DateFormat(CreateDate(2015, 1, 3), "m/d/yyyy")>
		<cfset workshopHolidays[8]["forbid"] = "dreamAct">
		<cfset workshopHolidays[9] = StructNew()>
		<cfset workshopHolidays[9]["name"] = "2015-2016 Dream Act Application not available online">
		<cfset workshopHolidays[9]["holiday"] = DateFormat(CreateDate(2015, 1, 4), "m/d/yyyy")>
		<cfset workshopHolidays[9]["forbid"] = "dreamAct">
		<cfreturn workshopHolidays />
	</cffunction>

	<cffunction name="getRoles" output="false" access="public" returntype="array">
		<cfset var roles = ArrayNew(1)>
		<cfset roles[1] = StructNew()>
		<cfset roles[1]["text"] = "Site Contact">
		<cfset roles[1]["value"] = "Site Contact">
		<cfset roles[1]["isAdminRole"] = false>
		<cfset roles[2] = StructNew()>
		<cfset roles[2]["text"] = "Organizer">
		<cfset roles[2]["value"] = "Organizer">
		<cfset roles[2]["isAdminRole"] = false>
		<cfset roles[3] = StructNew()>
		<cfset roles[3]["text"] = "Regional Admin">
		<cfset roles[3]["value"] = "Regional Admin">
		<cfset roles[3]["isAdminRole"] = true>
		<cfset roles[4] = StructNew()>
		<cfset roles[4]["text"] = "Super User">
		<cfset roles[4]["value"] = "Super User">
		<cfset roles[4]["isAdminRole"] = true>
		<cfreturn roles />
	</cffunction>
	
	<cffunction name="getPaymentTypes" output="false" access="public" returntype="array">
		<cfset var paymentTypes = ArrayNew(1)>
		<cfset paymentTypes[1] = StructNew()>
		<cfset paymentTypes[1]["text"] = "EFT">
		<cfset paymentTypes[1]["value"] = "EFT">
		<cfset paymentTypes[2] = StructNew()>
		<cfset paymentTypes[2]["text"] = "Check">
		<cfset paymentTypes[2]["value"] = "Check">
		<cfreturn paymentTypes />
	</cffunction>
	
	<cffunction name="getPaymentStatus" output="false" access="public" returntype="string">
		<cfargument name="Payment_ID" type="numeric" required="no" default="0" />
		<cfargument name="Scholarship_ID" type="numeric" required="no" default="0" />
		<cfargument name="Payment_Schedule_ID" type="numeric" required="no" default="0" />
		<cfscript>
			var paymentScheduleGateway = new assets.calgrants.com.gateway.PaymentScheduleGateway();
			var paymentGateway = new assets.calgrants.com.gateway.PaymentGateway();
			var qPayment = "";
			var paymentSchedule = "";
			if (arguments.Payment_ID gt 0) {
				qPayment = paymentGateway.query(Payment_ID = arguments.Payment_ID);
				paymentSchedule = paymentScheduleGateway.get(Payment_Schedule_ID = qPayment.Payment_Schedule_ID);
			} else {
				paymentSchedule = paymentScheduleGateway.get(Payment_Schedule_ID = arguments.Payment_Schedule_ID);
				qPayment = paymentGateway.query(Scholarship_ID = arguments.Scholarship_ID, Payment_Schedule_ID = paymentSchedule.getPayment_Schedule_ID());
			}
			var daysToSoftDeadline = DateDiff("d", Now(), paymentSchedule.getVerification_Soft_Deadline());
			var daysAfterSoftDeadline = daysToSoftDeadline * -1;
			var daysToHardDeadline = DateDiff("d", Now(), paymentSchedule.getVerification_Hard_Deadline());
			var daysAfterHardDeadline = daysToHardDeadline * -1;
		</cfscript>
		
		<cfif qPayment.RecordCount eq 0>
			<cfif daysAfterSoftDeadline gt 0>
				<cfif paymentSchedule.getVerification_Type() eq "Enrollment">
					<cfreturn "Late - class schedule " & daysAfterSoftDeadline & " days late" />
				<cfelseif paymentSchedule.getVerification_Type() eq "Performance">
					<cfreturn "Late - transcript " & daysAfterSoftDeadline & " days late" />
				</cfif>
			<cfelse>
				<cfreturn "Future payment" />
			</cfif>
		<cfelseif qPayment.Opt_Out eq 1>
			<cfreturn "No payment - " & qPayment.Opt_Out_Reason />
		<cfelseif IsDate(qPayment.Payment_Date)>
			<cfreturn "Paid on " & DateFormat(qPayment.Payment_Date, "m/d/yyyy") />
		<cfelseif IsDate(qPayment.Payment_Submitted_Date)>
			<cfreturn "Submitted on " & DateFormat(qPayment.Payment_Submitted_Date, "m/d/yyyy") />
		<cfelseif IsDate(qPayment.Approval_Date)>
			<cfreturn "Approved on " & DateFormat(qPayment.Approval_Date, "m/d/yyyy") />
		<cfelse>
			<cfif IsDate(qPayment.Institution1_Document_Date)>
				<cfif paymentSchedule.getVerification_Type() eq "Enrollment">
					<cfreturn "Received class schedule on " & DateFormat(qPayment.Institution1_Document_Date, "m/d/yyyy") />
				<cfelseif paymentSchedule.getVerification_Type() eq "Performance">
					<cfreturn "Received transcript on " & DateFormat(qPayment.Institution1_Document_Date, "m/d/yyyy") />
				</cfif>
			<cfelseif daysAfterSoftDeadline gt 0>
				<cfif paymentSchedule.getVerification_Type() eq "Enrollment">
					<cfreturn "Late - class schedule " & daysAfterSoftDeadline & " days late" />
				<cfelseif paymentSchedule.getVerification_Type() eq "Performance">
					<cfreturn "Late - transcript " & daysAfterSoftDeadline & " days late" />
				</cfif>
			<cfelse>
				<cfif paymentSchedule.getVerification_Type() eq "Enrollment">
					<cfreturn "Waiting on class schedule, " & daysToSoftDeadline & " days to deadline"/ >
				<cfelseif paymentSchedule.getVerification_Type() eq "Performance">
					<cfreturn "Waiting on transcript, " & daysToSoftDeadline & " days to deadline" />
				</cfif>
			</cfif>
		</cfif>
		<cfreturn "Status unknown" />
	</cffunction>
	
	<cffunction name="getPaymentStatuses" output="false" access="public" returntype="array">
		<cfset var paymentTypes = ArrayNew(1)>
		<cfset paymentTypes[1] = StructNew()>
		<cfset paymentTypes[1]["text"] = "Waiting">
		<cfset paymentTypes[1]["value"] = "Waiting">
		<cfset paymentTypes[2] = StructNew()>
		<cfset paymentTypes[2]["text"] = "Class Schedule Received">
		<cfset paymentTypes[2]["value"] = "Class Schedule Received">
		<cfset paymentTypes[3] = StructNew()>
		<cfset paymentTypes[3]["text"] = "Approved">
		<cfset paymentTypes[3]["value"] = "Approved">
		<cfset paymentTypes[4] = StructNew()>
		<cfset paymentTypes[4]["text"] = "Submitted">
		<cfset paymentTypes[4]["value"] = "Submitted">
		<cfset paymentTypes[5] = StructNew()>
		<cfset paymentTypes[5]["text"] = "Paid">
		<cfset paymentTypes[5]["value"] = "Paid">
		<cfreturn paymentTypes />
	</cffunction>
	
	<cffunction name="getPaymentMethods" output="false" access="public" returntype="array">
		<cfset var paymentMethods = ArrayNew(1)>
		<cfset paymentMethods[1] = StructNew()>
		<cfset paymentMethods[1]["text"] = "Electronic Funds Transfer (EFT)">
		<cfset paymentMethods[1]["value"] = "EFT">
		<cfset paymentMethods[2] = StructNew()>
		<cfset paymentMethods[2]["text"] = "Check">
		<cfset paymentMethods[2]["value"] = "Check">
		<cfset paymentMethods[3] = StructNew()>
		<cfset paymentMethods[3]["text"] = "Don't know">
		<cfset paymentMethods[3]["value"] = "DK">
		<cfreturn paymentMethods />
	</cffunction>
	
	<cffunction name="getAttendanceLevels" output="false" access="public" returntype="array">
		<cfset var attendanceLevels = ArrayNew(1)>
		<cfset attendanceLevels[1] = StructNew()>
		<cfset attendanceLevels[1]["text"] = "Full-time">
		<cfset attendanceLevels[1]["value"] = "Full-time">
		<cfset attendanceLevels[2] = StructNew()>
		<cfset attendanceLevels[2]["text"] = "Half-time">
		<cfset attendanceLevels[2]["value"] = "Half-time">
		<cfset attendanceLevels[3] = StructNew()>
		<cfset attendanceLevels[3]["text"] = "Part-time">
		<cfset attendanceLevels[3]["value"] = "Part-time">
		<cfreturn attendanceLevels />
	</cffunction>
	
	<cffunction name="getDegreeLevels" output="false" access="public" returntype="array">
		<cfset var degreeLevels = ArrayNew(1)>
		<cfset degreeLevels[1] = StructNew()>
		<cfset degreeLevels[1]["text"] = "Bachelors">
		<cfset degreeLevels[1]["value"] = "Bachelors">
		<cfset degreeLevels[2] = StructNew()>
		<cfset degreeLevels[2]["text"] = "Associates">
		<cfset degreeLevels[2]["value"] = "Associates">
		<cfset degreeLevels[3] = StructNew()>
		<cfset degreeLevels[3]["text"] = "Certificate">
		<cfset degreeLevels[3]["value"] = "Certificate">
		<cfreturn degreeLevels />
	</cffunction>
	
	<cffunction name="getUnits" output="false" access="public" returntype="array">
		<cfset var units = ArrayNew(1)>
		<cfloop index="i" from="1" to="24">
			<cfset units[i] = StructNew()>
			<cfset units[i]["text"] = i>
			<cfset units[i]["value"] = i>
		</cfloop>
		<cfreturn units />
	</cffunction>
	
	<cffunction name="getCredits" output="false" access="public" returntype="array">
		<cfset var credits = ArrayNew(1)>
		<cfloop index="i" from="1" to="24">
			<cfset credits[i] = StructNew()>
			<cfset credits[i]["text"] = i>
			<cfset credits[i]["value"] = i>
		</cfloop>
		<cfreturn credits />
	</cffunction>
	
	<cffunction name="getOptOutReasons" output="false" access="public" returntype="array">
		<cfset var optOutReasons = ArrayNew(1)>
		<cfset optOutReasons[1] = StructNew()>
		<cfset optOutReasons[1]["text"] = "Low units">
		<cfset optOutReasons[1]["value"] = "Low units">
		<cfset optOutReasons[2] = StructNew()>
		<cfset optOutReasons[2]["text"] = "Low GPA">
		<cfset optOutReasons[2]["value"] = "Low GPA">
		<cfset optOutReasons[3] = StructNew()>
		<cfset optOutReasons[3]["text"] = "Not enrolled">
		<cfset optOutReasons[3]["value"] = "Not enrolled">
		<cfset optOutReasons[4] = StructNew()>
		<cfset optOutReasons[4]["text"] = "Not AA/BA path">
		<cfset optOutReasons[4]["value"] = "Not AA/BA path">
		<cfset optOutReasons[5] = StructNew()>
		<cfset optOutReasons[5]["text"] = "Disqualified institution">
		<cfset optOutReasons[5]["value"] = "Disqualified institution">
		<cfset optOutReasons[6] = StructNew()>
		<cfset optOutReasons[6]["text"] = "Previously overpaid">
		<cfset optOutReasons[6]["value"] = "Previously overpaid">
		<cfset optOutReasons[7] = StructNew()>
		<cfset optOutReasons[7]["text"] = "Funds unavailable">
		<cfset optOutReasons[7]["value"] = "Funds unavailable">
		<cfset optOutReasons[8] = StructNew()>
		<cfset optOutReasons[8]["text"] = "Other reason">
		<cfset optOutReasons[8]["value"] = "Other reason">
		<cfreturn optOutReasons>
	</cffunction>
	
	<cffunction name="getVolunteerWorkshopStatuses" output="false" access="public" returntype="array">
		<cfset var paymentTypes = ArrayNew(1)>
		<cfset paymentTypes[1] = StructNew()>
		<cfset paymentTypes[1]["text"] = "Invited">
		<cfset paymentTypes[1]["value"] = "Invited">
		<cfset paymentTypes[2] = StructNew()>
		<cfset paymentTypes[2]["text"] = "Accepted">
		<cfset paymentTypes[2]["value"] = "Accepted">
		<cfset paymentTypes[3] = StructNew()>
		<cfset paymentTypes[3]["text"] = "Declined">
		<cfset paymentTypes[3]["value"] = "Declined">
		<cfset paymentTypes[4] = StructNew()>
		<cfset paymentTypes[4]["text"] = "Attended">
		<cfset paymentTypes[4]["value"] = "Attended">
		<cfset paymentTypes[5] = StructNew()>
		<cfset paymentTypes[5]["text"] = "Acknowledged">
		<cfset paymentTypes[5]["value"] = "Acknowledged">
		<cfreturn paymentTypes />
	</cffunction>
	
	<cffunction name="getRaceEthnicities" output="false" access="public" returntype="array">
		<cfset var child = "" />
		<cfset var raceEthnicities = ArrayNew(1)>
		<cfset raceEthnicities[1] = StructNew()>
		<cfset raceEthnicities[1]["text"] = "American Indian or Alaskan Native">
		<cfset raceEthnicities[1]["value"] = "American Indian or Alaskan Native">
		<cfset raceEthnicities[1]["children"] = ArrayNew(1)>
		<cfset raceEthnicities[2] = StructNew()>
		<cfset raceEthnicities[2]["text"] = "Asian">
		<cfset raceEthnicities[2]["value"] = "Asian">
		<cfset raceEthnicities[2]["children"] = ArrayNew(1)>
		<cfloop index="asianSub" list="Asian Indian,Cambodian,Chinese,Filipino,Hmong,Japanese,Korean,Laotian,Vietnamese">
			<cfset child = StructNew()>
			<cfset child["text"] = asianSub />
			<cfset child["value"] = asianSub />
			<cfset ArrayAppend(raceEthnicities[2].children, child) />
		</cfloop>
		<cfset raceEthnicities[3] = StructNew()>
		<cfset raceEthnicities[3]["text"] = "Black/African American">
		<cfset raceEthnicities[3]["value"] = "Black/African American">
		<cfset raceEthnicities[3]["children"] = ArrayNew(1)>
		<cfset raceEthnicities[4] = StructNew()>
		<cfset raceEthnicities[4]["text"] = "Hispanic/Latino">
		<cfset raceEthnicities[4]["value"] = "Hispanic/Latino">
		<cfset raceEthnicities[4]["children"] = ArrayNew(1)>
		<cfset raceEthnicities[5] = StructNew()>
		<cfset raceEthnicities[5]["text"] = "Native Hawaiian or Other Pacific Islander">
		<cfset raceEthnicities[5]["value"] = "Native Hawaiian or Other Pacific Islander">
		<cfset raceEthnicities[5]["children"] = ArrayNew(1)>
		<cfloop index="hawaiianSub" list="Guamanian,Hawaiian,Samoan,Tahitian">
			<cfset child = StructNew()>
			<cfset child["text"] = hawaiianSub />
			<cfset child["value"] = hawaiianSub />
			<cfset ArrayAppend(raceEthnicities[5].children, child) />
		</cfloop>
		<cfset raceEthnicities[6] = StructNew()>
		<cfset raceEthnicities[6]["text"] = "White">
		<cfset raceEthnicities[6]["value"] = "White">
		<cfset raceEthnicities[6]["children"] = ArrayNew(1)>
		<cfreturn raceEthnicities>
	</cffunction>
	
	<cffunction name="checkStock" output="false" access="public" returntype="any" hint="Returns custom min/max)">
		<cfargument name="Product_ID" type="numeric" required="no" default="0" />
		<cfargument name="productIDs" type="string" required="no" default="" />
		<cfargument name="Workshop_IDs" type="string" required="yes" />
		<cfargument name="Total_Anticipated_Students" type="numeric" required="yes" />
		<cfargument name="Unique_Volunteers" type="numeric" required="yes" />
		<cfscript>
			var productGateway = new assets.csac.com.gateway.ProductGateway();
			var products = productGateway.query();
			var stock = productGateway.checkStock(); 
			var customRule = "";
			var productID = "";
			for (i=1; i<=products.RecordCount; i++) {
				customRule = {};
				customRule.Rule = "";
				customRule.Minimum = "";
				customRule.Maximum = "";
				productID = products.Product_ID[i];
				// CCFC Custom Rules
				if (productID eq 13) {
					customRule.Rule = "10-100*Count(Workshop_IDs)";
					//customRule.Minimum = 0;
					customRule.Maximum = Int(100 * ListLen(arguments.Workshop_IDs));
				} else if (ListFind("2,3,5,12,16,18,25,26,28,55,56,57,58,59,60,61,63,65,66,67,68,69,70,71,72,80", productID) gt 0) {
					customRule.Rule = "10-300*Count(Workshop_IDs)";
					//customRule.Minimum = 0;
					customRule.Maximum = Int(300 * ListLen(arguments.Workshop_IDs));
				} else if (ListFind("35,36,45,47,48,49,50,51,52", productID) gt 0) {
					customRule.Rule = "10-500";
					//customRule.Minimum = 0;
					customRule.Maximum = 500;
				} else if (ListFind("14,34,44", productID) gt 0) {
					customRule.Rule = "50-2500";
					//customRule.Minimum = 0;
					customRule.Maximum = 2500;
				} else if (productID eq 22) {
					customRule.Rule = "5-200";
					//customRule.Minimum = 0;
					customRule.Maximum = 200;
				} else if (ListFind("7,24", productID) gt 0) {
					customRule.Rule = "1-Total_Anticipated_Students";
					//customRule.Minimum = 0;
					customRule.Maximum = Int(arguments.Total_Anticipated_Students);
				} else if (ListFind("1,8,9,10,11,17,19,20,21,23,29,30", productID) gt 0) {
					customRule.Rule = "1-Ceiling(1.1*Unique_Volunteers)";
					//customRule.Minimum = 0;
					customRule.Maximum = Int(Ceiling(arguments.Unique_Volunteers * 1.1));
				} else if (productID eq 82) {
					customRule.Rule = "1-100";
					//customRule.Minimum = 0;
					customRule.Maximum = 100;
				}
				if (IsNumeric(customRule.Maximum) and StructKeyExists(stock, productID) and customRule.Maximum lt stock[productID].Maximum) {
					stock[productID].Rule = ListAppend(stock[productID].Rule, customRule.Rule);
					stock[productID].Maximum = customRule.Maximum;
				}
			}
			return stock;
		</cfscript>
	</cffunction>
	<!--- *************************************************
	Misc functions
	************************************************* --->
    <cffunction name="toVoArray" output="false" access="public" returntype="array">
        <cfargument name="voName" type="string" required="true"/>
        <cfargument name="query" type="query" required="true"/>
        <cfscript>
            var voArray = arrayNew(1);
            var vo = "";
            var column = "";
            var loopCount = 0;
        </cfscript>
        <cfloop query="arguments.query">
            <cfset loopCount = loopCount + 1/>
            <cfset vo = structNew()/>
            <cfloop list="#arguments.query.ColumnList#" index="column">
                <cfset vo[column] = arguments.query[column][loopCount]/>
            </cfloop>
            <cfset arrayAppend(voArray, createObject(arguments.voName).init(argumentcollection=vo))/>
        </cfloop>
        <cfreturn voArray />
    </cffunction>

    <cffunction name="toVo" output="false" access="public" returntype="any">
        <cfargument name="voName" type="string" required="true"/>
        <cfargument name="query" type="query" required="true"/>
        <cfset var vo = StructNew()/>
        <cfset var column = "" />
        <cfloop list="#arguments.query.ColumnList#" index="column">
            <cfset vo[column] = arguments.query[column][1]/>
        </cfloop>
        <cfreturn createObject(arguments.voName).init(argumentcollection=vo) />
    </cffunction>
	
	<cffunction name="processTrigger" access="public" returntype="any">
		<cfargument name="trigger" type="string" required="yes" />
		<cfargument name="data" type="any" required="no" default="" />
		<cfscript>
			switch(arguments.trigger) {
				case "newOrderNotification":
					return emailNewOrderNotification(argumentCollection = arguments.data);
				break;
				case "approvedOrderNotification":
					return emailApprovedOrderNotification(argumentCollection = arguments.data);
				break;
				case "shippedOrderNotification":
					return emailShippedOrderNotification(argumentCollection = arguments.data);
				break;
			}
			return false;
		</cfscript>
	</cffunction>
	
	<!--- retired for generateClaimID
	<cffunction name="genEvaluationCode" output="false" access="public" returntype="string">
		<!--- to do: need to validate that generated code isn't already in use.  have had problems with duplicates --->
		<cfset var pickList = "a,b,c,d,e,f,g,h,i,j,k,m,n,p,q,r,s,t,u,v,w,x,y,z,2,3,4,5,6,7,8,9" />
		<cfset var evaluationCode = "" />
		<cfloop index="i" from="1" to="8">
			<cfset evaluationCode = evaluationCode & ListGetAt(picklist, RandRange(1, ListLen(pickList), "SHA1PRNG")) />
		</cfloop>
		<cfreturn evaluationCode />
	</cffunction>
	--->
	
	<cffunction name="generateClaimID" output="false" access="public" returntype="any">
		<cfargument name="Scholarship_ID" type="numeric" required="yes" hint="Scholarship_ID is required and used to see the algorithm that generates the Claim_ID" />
		<!---
		A Claim ID is generated from data associated with an undeleted Scholarship record and therefore a Scholarship corresponding to
		arguments.Scholarship_ID must exist before a Claim ID can be generated.  If the Scholarship does not exist, the function
		returns an empty string.
		
		This function does not update the corresponding Scholarship record and will produce a unique value each time it is called.
		
		The algorithm key is:
		
		First character is the capitalized first letter of the value of Student.Origin or X for Scholarship with no corresponding Student record. For example,
			P = Student.Origin is "Paper"
			W = Student.Origin is "Web"
			X = No Student record
		Next n characters are the value of arguments.Scholarship_ID
		Next 5 characters are a random numeric string without 1 or 0 (because they can be confused with lowercase L and capital O)
		
		A possible result where arguments.Scholarship_ID = 45670 is "W4567057849"
		--->
		<cfscript>
			local.scholarshipGateway = new assets.calgrants.com.gateway.ScholarshipGateway();
			local.scholarship = local.scholarshipGateway.get(Scholarship_ID = arguments.Scholarship_ID);
			local.retVal = "";
			local.randStringLen = 5;
			local.randString = generateRandomAlphaNumeric(strLen = local.randStringLen, composition = "numeric", excludeAlikes = true);
			if (local.scholarship.getScholarship_ID() gt 0 and local.scholarship.getDeleted() eq 0) {
				if (Len(Trim(local.scholarship.getStudent_ID())) gt 0 and local.scholarship.getStudent_ID() gt 0) {
					local.studentGateway = new assets.calgrants.com.gateway.StudentGateway();
					local.student = local.studentGateway.get(Student_ID = local.scholarship.getStudent_ID());
					if (Len(Trim(local.student.getOrigin())) gt 0) {
						local.retVal = UCase(Left(local.student.getOrigin(), 1));
					} else {
						local.retVal = "X";
					}
				} else {
					local.retVal = "X";
				}
				local.retVal = local.retVal & arguments.Scholarship_ID & local.randString;
			}
			return local.retVal;
		</cfscript>
	</cffunction>
	
	<cffunction name="generateRandomAlphaNumeric" output="false" access="public" returntype="string" hint="Generates a random alphanumeric string">
		<cfargument name="strLen" type="numeric" required="no" default="5" />
		<cfargument name="composition" type="string" required="no" default="numeric" hint="Controls if letters, numbers or both are returned" />
		<cfargument name="capsOnly" type="boolean" required="no" default="true" />
		<cfargument name="excludeAlikes" type="boolean" required="no" default="true" hint="Alikes are I (capital I), O (capital O), o (lower case o), l (lower case L), 1 (one) and 0 (zero)" />
		<cfscript>
			local.lowercaseAlphas = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z";
			local.lowercaseAlphasNoAlikes = "a,b,c,d,e,f,g,h,i,j,k,m,n,p,q,r,s,t,u,v,w,x,y,z";
			local.uppercaseAlphas = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z";
			local.uppercaseAlphasNoAlikes = "A,B,C,D,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,U,V,W,X,Y,Z";
			local.numerics = "1,2,3,4,5,6,7,8,9,0";
			local.numericsNoAlikes = "2,3,4,5,6,7,8,9";
			local.pickList = "";
			local.retVal = "";
			if (arguments.capsOnly) {
				if (arguments.excludeAlikes) {
					local.pickList = IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.uppercaseAlphasNoAlikes), DE("")) & IIf(arguments.composition eq "numeric" or arguments.composition eq "alphanumeric", DE(local.numericsNoAlikes), DE(""));
				} else {
					local.pickList = IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.uppercaseAlphas), DE("")) & IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.numerics), DE(""));
				}
			} else {
				if (arguments.excludeAlikes) {
					local.pickList = IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.lowercaseAlphasNoAlikes), DE("")) & IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.uppercaseAlphasNoAlikes), DE("")) & IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.numericsNoAlikes), DE(""));
				} else {
					local.pickList = IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.lowercaseAlphas), DE("")) & IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.uppercaseAlphas), DE("")) & IIf(arguments.composition eq "alpha" or arguments.composition eq "alphanumeric", DE(local.numerics), DE(""));
				}
			}
			for (var idx=1; idx <= arguments.strLen; idx++) {
				local.retVal = local.retVal & ListGetAt(local.pickList, RandRange(1, ListLen(local.pickList), "SHA1PRNG"), ",");	
			}
			return local.retVal;
		</cfscript>
	</cffunction>
	
	<cffunction name="generatePNG" output="false" access="public" returntype="string" hint="Creates a PNG image from a Base64 string at a destination">
		<cfargument name="pngBase64String" type="string" required="yes" />
		<cfargument name="destination" type="string" required="yes" />
		<!--- <cfscript>
			var pngBase64 = ImageReadBase64(arguments.pngBase64String);
			var pngImage = ImageNew(pngBase64);
			ImageWriteBase64(pngImage, arguments.destination, "png", "yes", true);
		</cfscript> --->
		<cfimage source="#arguments.pngBase64String#" destination="#arguments.destination#" action="write" isBase64="yes" overwrite="yes">
		<cfreturn arguments.destination />
	</cffunction>
	
	<cffunction name="uploadFormFile" output="false" access="public" returntype="any">
		<cfargument name="fileField" type="string" required="yes" />
		<cfargument name="destination" type="string" required="no" default="#request.tempDirectory#" />
		<cfset var uploadedFile = "" />
		<!--- ensure destination directory exists --->
		<!--- <cfset var filePathPattern = "^[a-zA-Z]:(\\[a-zA-Z0-9_]+)+$" />
		IsValid("regex", arguments[arguments.fileField], filePathPattern) and  --->
		<cfif NOT DirectoryExists(arguments.destination)>
			<cfdirectory action="create" directory="#arguments.destination#">
		</cfif>
		<!--- although it is not listed in the arguments, this function requires an argument that is named the value of arguments.fileField and is a form.input.file type --->
		<cffile action="upload" destination="#arguments.destination#" filefield="#arguments.fileField#" nameconflict="makeunique">
		<cfreturn cffile/>
	</cffunction>
	
	<cffunction name="scoreStrings" output="false" access="public" returntype="any">
		<cfargument name="string1" type="string" required="yes" hint="String to be compared" />
		<cfargument name="string2" type="string" required="yes" hint="String used for comparison" />
		<cfargument name="maxOffset" type="numeric" required="no" default="1" hint="Number of places to shift when scoring a match" />
		<cfscript>
			/*
				Compare Strings
				
				Arguments
					string1		First string to be compared
					string2		Second string to be compared
					maxOffset	Maximum number of characters to look ahead in comparing strings
								
				Returns struct
					string1		Value for string1 with highlights
					string2		Value for string2 with highlights
					lcs			Longest common string within maxOffset
					distance	Average string length minus the longest string
					score		Distance divided by len of lcs
			*/
			var c = 0;
			var offset1 = 0;
			var offset2 = 0;
			var lcs = 0;
			var _string1 = createObject("java", "java.lang.StringBuffer").init(javacast("int", len(string1)*3));
			var _string2 = createObject("java", "java.lang.StringBuffer").init(javacast("int", len(string2)*3));
			// surround with...
			var h1 = '<span class="hl">';
			var h2 = '</span>';
			//var return_struct = structNew();
			
			var i = 0;
			var str1Ch = "";
			var str2Ch = "";
			
			var ret = structNew();
			var ret.vars = structNew();
			ret.vars.string1 = arguments.string1; // should we strip away non alphanumeric characters?
			ret.vars.string2 = arguments.string2;
			
			ret.vars.string1Len = len(ret.vars.string1);
			ret.vars.string2Len = len(ret.vars.string2);
			
			if (ret.vars.string1Len eq 0 or ret.vars.string2Len eq 0) {
				ret.commonality = 0;
				ret.continuity = 0;
				ret.difference = 0;
				ret.score = 0;
				ret.similarity = 0;
				ret.string1 = iif(ret.vars.string1Len gt 0, DE(h1 & ret.vars.string1 & h2), DE(""));
				ret.string2 = iif(ret.vars.string2Len gt 0, DE(h1 & ret.vars.string2 & h2), DE(""));
				return ret;
			}
			
			ret.vars.string1DistinctChars = ""; // list of chars in string1, no repeat
			ret.vars.string1CommonChars = ""; // list of string1 chars found in string2
			ret.vars.string1UniqueChars = ""; // list of string1 chars not found in string2
			ret.vars.string2DistinctChars = "";
			ret.vars.string2CommonChars = "";
			ret.vars.string2UniqueChars = "";
			//ret.vars.commonChars = 0; // list of chars that are in both string1 and string2

			// string1DistinctChars
			for (i=1; i lte Len(ret.vars.string1); i++) {
				str1Ch = Mid(ret.vars.string1, i, 1);
				if (ListFind(ret.vars.string1DistinctChars, str1CH, "|") eq 0) {
					ret.vars.string1DistinctChars = ListAppend(ret.vars.string1DistinctChars, str1Ch, "|");
				}
			}
			// string2DistinctChars, string2CommonChars, string2UniqueChars
			for (i=1; i lte Len(ret.vars.string2); i++) {
				str2Ch = Mid(ret.vars.string2, i, 1);
				if (ListFind(ret.vars.string2DistinctChars, str2Ch, "|") eq 0) {
					ret.vars.string2DistinctChars = ListAppend(ret.vars.string2DistinctChars, str2Ch, "|");
					if (ListFind(ret.vars.string1DistinctChars, str2Ch, "|") gt 0 and ListFind(ret.vars.string2CommonChars, str2Ch, "|") eq 0) {
						ret.vars.string2CommonChars = ListAppend(ret.vars.string2CommonChars, str2Ch, "|");
					} else if (ListFind(ret.vars.string2UniqueChars, str2Ch, "|") eq 0) {
						ret.vars.string2UniqueChars = ListAppend(ret.vars.string2UniqueChars, str2Ch, "|");
					}
				}
			}
			// string1CommonChars and string1UniqueChars
			for (i=1; i lte ListLen(ret.vars.string1DistinctChars, "|"); i++) {
				str1Ch = ListGetAt(ret.vars.string1DistinctChars, i, "|");
				if (ListFind(ret.vars.string2DistinctChars, str1Ch, "|") gt 0 and ListFind(ret.vars.string1CommonChars, str1Ch, "|") eq 0) {
					ret.vars.string1CommonChars = ListAppend(ret.vars.string1CommonChars, str1Ch, "|");
				} else if (ListFind(ret.vars.string1UniqueChars, str1Ch, "|") eq 0) {
					ret.vars.string1UniqueChars = ListAppend(ret.vars.string1UniqueChars, str1Ch, "|");
				}
			}
			ret.vars.string1CharSimilarity = ListLen(ret.vars.string1CommonChars, "|")/ListLen(ret.vars.string1DistinctChars, "|");
			ret.vars.string1CharDifference = ListLen(ret.vars.string1UniqueChars, "|")/ListLen(ret.vars.string1DistinctChars, "|");
			ret.vars.string2CharSimilarity = ListLen(ret.vars.string2CommonChars, "|")/ListLen(ret.vars.string2DistinctChars, "|");
			ret.vars.string2CharDifference = ListLen(ret.vars.string2UniqueChars, "|")/ListLen(ret.vars.string2DistinctChars, "|");
			ret.similarity = (ret.vars.string1CharSimilarity + ret.vars.string2CharSimilarity) / 2;
			ret.difference = (ret.vars.string1CharDifference + ret.vars.string2CharDifference) / 2;
			
			// LCS and highlighted strings
			// c = counter, offset1 is the offset on string1 matching, follows for string2
			while ((c + offset1 lt len(string1)) and (c + offset2 lt len(string2))) {
				// get next character or set of characters
				next_string1 = mid(string1, c + offset1 + 1, iif(not c, 3, 1)); // First time through check the first three
				next_string2 = mid(string2, c + offset2 + 1, iif(not c, 3, 1)); // First time through check the first three
				// characters are equal
				if (compare(next_string1, next_string2) eq 0) {
					// increment LCS
					lcs = lcs + 1;
					// append to highlighed versions
					_string1.append(left(next_string1, 1));
					_string2.append(left(next_string2, 1));
				// characters not equal
				} else {
					// create a reference to last offset
					old_offset1 = offset1;
					old_offset2 = offset2;
					_string1_deviation = "";
					_string2_deviation = "";
					// compare until maxOffset
					for (i = 0; i lt maxOffset; i++) {
						next_string1 = mid(string1, c + offset1 + i + 1, 3); // increments each loop
						len_next_string1 = len(next_string1);
						bookmarked_string1 = mid(string1, c + offset1 + 1, 3); // does not increment
						next_string2 = mid(string2, c + offset2 + i + 1, 3); // increments each loop
						len_next_string2 = len(next_string2);
						bookmarked_string2 = mid(string2, c + offset2 + 1, 3); // does not increment
						// at end of both strings
						if (not len_next_string1 and not len_next_string2) {
							// no more to compare, break
							break;
						}
						// keep track of deviation from origin
						_string1_deviation = _string1_deviation & left(next_string1, 1);
						_string2_deviation = _string2_deviation & left(next_string2, 1);
						// string1 has downstream match
						if (compare(next_string1, bookmarked_string2) eq 0) {
							// offset of string1 from string2
							offset1 =  offset1 + i;
							// increment LCS
							lcs = lcs + 1;
							// found match, break
							break;
						}
						// string2 has downstream match
						if (compare(next_string2, bookmarked_string1) eq 0) {
							// offset of string2 from string1
							offset2 = offset2 + i;
							// increment LCS
							lcs = lcs + 1;
							// found match, break
							break;
						}
					}
					// count of inserted characters
					added_offset1 = offset1 - old_offset1;
					added_offset2 = offset2 - old_offset2;
					// no match after exhausing maxOffset
					if(added_offset1 eq 0 and added_offset2 eq 0) {
						_string1.append(h1 & left(_string1_deviation, added_offset1+1) & h2);
						_string2.append(h1 & left(_string2_deviation, added_offset2+1) & h2);
					// string2 has inserted characters
					} else if (added_offset1 eq 0 and added_offset2 gt 0) {
						_string1.append(left(_string1_deviation, 1));
						_string2.append(h1 & left(_string2_deviation, added_offset2) & h2 & right(_string2_deviation, 1));
					// string1 has inserted characters
					} else if (added_offset1 gt 0 and added_offset2 eq 0) {
						_string1.append(h1 & left(_string1_deviation, added_offset1) & h2 & right(_string1_deviation, 1));
						_string2.append(left(_string2_deviation, 1));
					}
				}
				c = c + 1;
			}
			// extra characters at end of string1
			if(c + offset1 lt len(string1)) {
				_string1.append(h1 & right(string1, len(string1) - (c + offset1)) & h2);
			}
			// extra characters at the end of string2
			if(c + offset2 lt len(string2)) {
				_string2.append(h1 & right(string2, len(string2) - (c + offset2)) & h2);
			}
			
			ret.vars.lcs = lcs;
			ret.string1 = _string1.toString();
			ret.string2 = _string2.toString();
			ret.vars.maxOffset = maxOffset;
			ret.vars.distance = (ret.vars.string1Len + ret.vars.string2Len) / 2 - ret.vars.lcs;
			if (ret.vars.lcs eq 0) {
				ret.continuity = 0;
			} else if (ret.vars.distance eq 0) {
				ret.continuity = 1;
			} else {
				ret.continuity = ret.vars.lcs / ((ret.vars.string1Len + ret.vars.string2Len) / 2);
			}
			ret.vars.maxLen = iif(ret.vars.string1Len gte ret.vars.string2Len, de(ret.vars.string1Len), de(ret.vars.string2Len));
			if (ret.vars.lcs eq 0) {
				ret.commonality = 0;
			} else if (ret.vars.distance eq 0) {
				ret.commonality = 1;
			} else {
				ret.commonality = 1 - (ret.vars.distance/ret.vars.maxLen);
			}
			ret.score = (ret.similarity + 2*ret.continuity + 3*ret.commonality) / 6;

			/*ret.minLen = IIf(ret.string1Len lte ret.string2Len, DE(ret.string1Len), DE(ret.string2Len));
			ret.maxLen = IIf(ret.string1Len gte ret.string2Len, DE(ret.string1Len), DE(ret.string2Len));
			ret.aveLen = ret.string1Len + ret.string2Len / 2;
			
			ret.string1CommonChars = "";
			ret.string1DifferentChars = "";
			ret.string2CommonChars = "";
			ret.string2DifferentChars = "";
			// similarity - % of common characters to distinct characters in string1
			ret.similarity = 0;
			// difference - % of characters in string1 not found in string2
			ret.difference = 0;
			// string1 -> string2 common chars
			for (sch in ret.string1UniqueChars) {
				if (ListFind(ret.string2UniqueChars, sch) gt 0) {
					ret.string1CommonChars = ListAppend(ret.string1CommonChars, sch);
				} else {
					ret.string1DifferentChars = ListAppend(ret.string1DifferentChars, sch);
				}
			}
			// string1 -> string2 different chars
			for (sch in ret.string1UniqueChars) {
				if (ListFind(ret.string2UniqueChars, sch) gt 0) {
					ret.string1CommonChars = ListAppend(ret.string1CommonChars, sch);
				} else {
					ret.string1DifferentChars = ListAppend(ret.string1DifferentChars, sch);
				}
			}

			ret.lcs = 0;
			ret.distance = 0;
			
			ret.communality = 0;
			ret.continuity = 0;
			ret.exactMatches = 0;
			ret.complexMatches = 0;*/
			
			return ret;
		
			/******************************************************************
			// string1 and string2 both empty 
			if (not len(trim(string1)) and not len(trim(string2))) {	
				return_struct.lcs = 0;
				return_struct.score = 0;
				return_struct.distance = 0;
				return_struct.maxLen = 0;
				return_struct.string1 = "";
				return_struct.string2 = "";
			// string2 empty
			} else if (len(trim(string1)) and not len(trim(string2))) {
				return_struct.lcs = 0;
				return_struct.score = 0;
				return_struct.distance = 0;
				return_struct.maxLen = len(string1);
				return_struct.string1 = string1;
				return_struct.string2 = "";
			// string1 empty
			} else if (len(trim(string2)) and not len(trim(string1))) {
				return_struct.lcs = 0;
				return_struct.score = 0;
				return_struct.distance = 0;
				return_struct.maxLen = len(string2);
				return_struct.string1 = "";
				return_struct.string2 = string2;
			// neither string1 nor string2 are empty
			} else {
				// compare strings, one character at a time, including with offsets 
				// to do: stop offset after 100% match found?
				while ((c + offset1 lt len(string1)) and (c + offset2 lt len(string2))) {
					// get next character
					next_string1 = mid(string1, c + offset1+1, iif(not c, 3, 1)); // First time through check the first three
					next_string2 = mid(string2, c + offset2+1, iif(not c, 3, 1)); // First time through check the first three
					// characters are equal
					if (compare(next_string1, next_string2) eq 0) {
						// increment LCS
						lcs = lcs + 1;
						// append to highlighed versions
						_string1.append(left(next_string1, 1));
						_string2.append(left(next_string2, 1));
					// characters not equal
					} else {
						// create a reference to last offset
						old_offset1 = offset1;
						old_offset2 = offset2;
						_string1_deviation = "";
						_string2_deviation = "";
						// compare until maxOffset
						for (i = 0; i lt maxOffset; i=i+1) {
							next_string1 = mid(string1, c + offset1 + i+1, 3); // increments each loop
							len_next_string1 = len(next_string1);
							bookmarked_string1 = mid(string1, c + offset1+1, 3); // does not increment
							next_string2 = mid(string2,c + offset2 + i+1,3); // increments each loop
							len_next_string2 = len(next_string2);
							bookmarked_string2 = mid(string2, c + offset2+1, 3); // does not increment
							// at end of both strings
							if (not len_next_string1 and not len_next_string2) {
								// no more to compare, break
								break;
							}
							// keep track of deviation from origin
							_string1_deviation = _string1_deviation & left(next_string1, 1);
							_string2_deviation = _string2_deviation & left(next_string2, 1);
							// string1 has downstream match
							if (compare(next_string1, bookmarked_string2) eq 0) {
								// offset of string1 from string2
								offset1 =  offset1+i;
								// increment LCS
								lcs = lcs + 1;
								// found match, break
								break;
							}
							// string2 has downstream match
							if (compare(next_string2, bookmarked_string1) eq 0) {
								// offset of string2 from string1
								offset2 = offset2+i;
								// increment LCS
								lcs = lcs + 1;
								// found match, break
								break;
							}
						}
						// count of inserted characters
						added_offset1 = offset1 - old_offset1;
						added_offset2 = offset2 - old_offset2;
						// no match after exhausing maxOffset
						if(added_offset1 eq 0 and added_offset2 eq 0) {
							_string1.append(h1 & left(_string1_deviation, added_offset1+1) & h2);
							_string2.append(h1 & left(_string2_deviation, added_offset2+1) & h2);
						// string2 has inserted characters
						} else if (added_offset1 eq 0 and added_offset2 gt 0) {
							_string1.append(left(_string1_deviation, 1));
							_string2.append(h1 & left(_string2_deviation, added_offset2) & h2 & right(_string2_deviation, 1));
						// string1 has inserted characters
						} else if (added_offset1 gt 0 and added_offset2 eq 0) {
							_string1.append(h1 & left(_string1_deviation, added_offset1) & h2 & right(_string1_deviation, 1));
							_string2.append(left(_string2_deviation, 1));
						}
					}
					c = c + 1;
				}
				// extra characters at end of string1
				if(c + offset1 lt len(string1)) {
					_string1.append(h1 & right(string1, len(string1)-(c + offset1)) & h2);
				}
				// extra characters at the end of string2
				if(c + offset2 lt len(string2)) {
					_string2.append(h1 & right(string2, len(string2)-(c + offset2)) & h2);
				}
				// distance - average string length minus LCS - will be zero with perfect matches
				distance = (len(string1) + len(string2))/2 - lcs;
				// maxLen - longest original string
				maxLen = iif(len(string1) gt len(string2), de(len(string1)), de(len(string2)));
				// score - empty string scores 1, otherwise 1 minus distance divided by maxLen
				if (lcs eq 0) {
					score = 0;
				} else if (lcs gt 0 and distance eq 0) {
					score = 1;
				} else {
					//score = iif(maxLen eq 0, 1, 1-(distance/maxLen));
					score = 1 - (distance/maxLen);
				}
				// return results
				return_struct.lcs = lcs;
				return_struct.score = score;
				return_struct.distance = distance;
				return_struct.maxLen = maxLen;
				return_struct.string1 = _string1.toString(); // "highlighted" version
				return_struct.string2 = _string2.toString(); // "highlighted" version
			}
			return return_struct;
			******************************************************************/
		</cfscript>
	</cffunction>
	
	<cffunction name="scoreStructs" output="false" access="public" returntype="any">
		<cfargument name="struct1" type="struct" required="yes" hint="Struct to be compared" />
		<cfargument name="struct2" type="struct" required="yes" hint="Struct used for comparison" />
		<cfargument name="config" type="struct" required="no" default="#StructNew()#" hint="Struct of structs with one for each key in struct1 containing keys - multiplier, maxOffset" />
		<cfscript>
			/*
				Compare Structs
			
				Arguments
					struct1				Struct that is the basis for comparison or master struct. All the properties in struct1 will be compared regardless of what's in struct2
					struct2				Struct containing comparison values. Missing properties will score 0 and extra properties will be ignored.
					config				Struct containing configuration for each comparison/scoring of each property in struct1 and struct2
						<property>..n		Each property in struct1 has a corresponding property in config, that is also a struct
							multiplier			Integer that weights the score for a property relative to the other properties on any scale
							maxOffset			Pass through to string comparison function
								
				Returns struct
					<property>..n		Each property in struct1 is returned as a struct including the values returned from the string comparison
						string1				Value for s1 with highlights
						string2				Value for s2 with highlights
						lcs					Longest common string
						distance			Average string length minus the longest string
						score				Distance divided by len of lcs
						weightedScore		Score multiplied by config.multiplier
						multiplier			Passed back from config.multiplier for reference
					maxScore			Maximum score possible - equal to the number of properties
					maxWeightedScore	Maximum score possible with multipliers
					totalScore			Sum of scores from all properties
					totalWeightedScore	Sum of scores from all properies with multipliers
			*/
			var ret = structNew();
			ret.commonality = 0;
			ret.continuity = 0;
			ret.difference = 0;
			ret.score = 0;
			ret.similarity = 0;
			ret.perfect = 0;
			ret.bonus = 0;
			ret.struct1 = arguments.struct1;
			ret.struct2 = arguments.struct2;
			ret.config = arguments.config;
			ret.result = structNew();
			for (prop in ret.struct1) {
				if (NOT StructKeyExists(ret.config, prop)) {
					ret.config[prop] = structNew();					
				};
				if (NOT StructKeyExists(ret.config[prop], "maxOffset")) {
					ret.config[prop].maxOffset = 3;
				};
				ret.result[prop] = scoreStrings(ret.struct1[prop], ret.struct2[prop], ret.config[prop].maxOffset);//return_struct.config[prop].maxOffset
				ret.commonality = ret.commonality + ret.result[prop].commonality;
				ret.continuity = ret.continuity + ret.result[prop].continuity;
				ret.difference = ret.difference + ret.result[prop].difference;
				ret.score = ret.score + ret.result[prop].score;
				ret.similarity = ret.similarity + ret.result[prop].similarity;
				ret.perfect = ret.perfect + iif(ret.result[prop].score eq 1, DE(1), DE(0));
				ret.bonus = ret.bonus + iif((prop eq "address" or prop eq "phone1" or prop eq "phone2" or prop eq "phone" or prop eq "email") and ret.result[prop].score eq 1, DE(1), DE(0));
			}
			ret.propertyCount = ListLen(StructKeyList(ret.result));
			ret.commonality = ret.commonality / ret.propertyCount;
			ret.continuity = ret.continuity / ret.propertyCount;
			ret.difference = ret.difference / ret.propertyCount;
			ret.score = ret.score / ret.propertyCount;
			ret.similarity = ret.similarity / ret.propertyCount;
			return ret;
		</cfscript>
	</cffunction>
	
	<cffunction name="pickPhone" output="false" access="public" returntype="string">
		<cfargument name="phone" type="string" required="yes" />
		<cfargument name="phone1" type="string" required="yes" />
		<cfargument name="phone2" type="string" required="yes" />
		<cfscript>
			var phone1Score = scoreStrings(arguments.phone, arguments.phone1, 3);
			var phone2Score = scoreStrings(arguments.phone, arguments.phone2, 3);
			if (phone1Score.score gte phone2Score.score) {
				return "phone1";
			} else {
				return "phone2";
			}
		</cfscript>		
	</cffunction>

</cfcomponent>

<!---
Retired functions
 <cffunction name="emailOrderApproval" output="false" access="public" returntype="boolean">
		<cfargument name="orderID" type="numeric" />
		<cfset var order = "" />
		<cfset var organization = "" />
		<cfset var orderedBy = "" />
		<cfset var recipients = "" />
		<cfset var subject = "" />
		<cfset var message = "" />
		<cfinvoke component="assets.calgrants.com.gateway.OrderGateway" method="get" returnvariable="order">
			<cfinvokeargument name="Order_ID" value="#orderID#" />
		</cfinvoke>
		<cfinvoke component="assets.calgrants.com.gateway.OrganizationGateway" method="get" returnvariable="organization">
			<cfinvokeargument name="Organization_ID" value="#order.getOrganization_ID()#" />
		</cfinvoke>
		<cfinvoke component="assets.calgrants.com.gateway.UserGateway" method="get" returnvariable="orderedBy">
			<cfinvokeargument name="User_ID" value="#order.getOrdered_By_ID()#" />
		</cfinvoke>
		<cfset recipients = orderedBy.getEmail() />
		<cfset subject = "Your Cash for College order ###arguments.orderID# has been approved">
		<cfoutput>
			<cfsavecontent variable="message">
				<p>Your Cash for College order ###order.getOrder_ID()# for #organization.getOrganization()# placed on #DateFormat(order.getDate_Created(), 'dddd, mmmm d, yyyy')# has been approved.</p>
				<p><a href="https://www.calgrants.org/organizers">Login to your account</a></p>
				<p>If the link above does not work, copy and paste the following URL into your web browser:</p>
				<p>https://www.calgrants.org/organizers</p>
				<p>If you have forgotten your password, click the &quot;Forgot your password?&quot; link on the login page.</p>
			</cfsavecontent>
		</cfoutput>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>
	
	<cffunction name="emailOrderToFulfillment" output="false" access="public">
		<cfargument name="orderID" type="numeric">
		<cfset var recipients = request.fulfillment.email />
		<cfset var subject = "Cash for College Order ###arguments.orderID#" />
		<cfset var message = "" />
		<cfset variables.orderID = arguments.orderID />
		<cfset variables.mailable = true />
		<cfsavecontent variable="message">
			<cfinclude template="#request.pathToUI#/details/order.cfm">
		</cfsavecontent>
		<cfreturn sendEmail(recipients, subject, message, "HTML")>
	</cffunction>
	
	<cffunction name="sendEmail" access="public" returntype="boolean">
		<cfargument name="recipients" type="string" required="yes" hint="One email or a comma-separated list of emails">
		<cfargument name="subject" type="string" required="yes">
		<cfargument name="message" type="string" required="yes">
		<cfargument name="emailType" type="string" required="no" default="HTML">
		<cfset local.result = true />
		<cfset local.recipients = arguments.recipients />
		<cfset local.bcc = request.developer.email />
		<cfset local.from = request.personality.email />
		<cfset local.replyTo = request.personality.email />
		<cfif request.mode eq "dev">
			<cfset local.recipients = request.developer.email>
			<cfset local.bcc = "">
		</cfif>
		<cftry>
			<cfloop index="recipient" list="#local.recipients#">
				<cfmail to="#recipient#" bcc="#local.bcc#" from="#local.from#" replyto="#local.replyTo#" subject="#arguments.subject#" type="#arguments.emailType#">
					<cfif arguments.emailType eq "HTML">
						#arguments.message#
					<cfelseif arguments.emailType eq "Plain Text">
						#arguments.message#
					</cfif>
				</cfmail>
				<cfif request.mode eq "dev">
					<cflog text="#arguments.message#" file="MailLogger" type="information">
				</cfif>
			</cfloop>
			<cfcatch type="any">
				<cfset local.result = false />
			</cfcatch>
		</cftry>
		<cfreturn local.result>
	</cffunction>
	
	<cffunction name="sendDebugEmail" access="public" returntype="void">
		<cfargument name="error" type="any" required="no">
		<cfmail to="#request.devEmail#" from="noreply@calgrants.org" subject="CCFC Scholarship Claim Site Error" type="html">
			<cfdump var="#arguments.error#">
		</cfmail>
	</cffunction>
	
	<cffunction name="pingURL" access="public" returntype="any">
		<cfargument name="URL" required="yes" type="string">
		<cfset var retVal = "">
		<cftry>
			<cfexecute name="ping.exe" arguments="-n 1 #arguments.URL#" timeout="5" variable="pingResult"></cfexecute>
			<cfset retVal = pingResult>
			<cfcatch type="any">
				<cfset retVal = cfcatch.Detail>
				<cfabort>
			</cfcatch>
		</cftry>
		<cfreturn retVal>
	</cffunction>
	
	<cffunction name="getStatesAbbreviationList" access="public">
		<cfreturn "AL,AK,AZ,AR,CA,CO,CT,DC,DE,FL,GA,HI,ID,IL,IN,IA,KS,KY,LA,ME,MD,MA,MI,MN,MS,MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,OH,OK,OR,PA,RI,SC,SD,TN,TX,UT,VT,VA,WA,WV,WI,WY">
	</cffunction>
	
	<cffunction name="checkStock" output="false" access="public" returntype="any">
		<cfargument name="Product_ID" type="numeric" required="no" default="0" />
		<cfargument name="productIDs" type="string" required="no" default="" />
		<cfargument name="Workshop_IDs" type="string" required="yes" />
		<cfargument name="Total_Anticipated_Students" type="numeric" required="yes" />
		<cfargument name="Unique_Volunteers" type="numeric" required="yes" />
		<cfscript>
			var ret = StructNew();
			var products = "";
			var stock = "";
			var i = "";
			var productGateway = new assets.csac.com.gateway.ProductGateway();
			if (arguments.Product_ID gt 0) {
				products = productGateway.query(Product_ID = arguments.Product_ID);
			} else if (Len(Trim(arguments.productIDs)) gt 0) {
				products = productGateway.query(productIDs = arguments.productIDs);
			} else {
				products = productGateway.query(Active = 1);	
			}
			for (i=1; i<=products.RecordCount; i++) {
				stock = StructNew();
				stock.Product_ID = products.Product_ID[i];
				stock.Product = products.Product[i];
				stock.Category = products.Category[i];
				stock.Subcategory = products.Subcategory[i];
				stock.Download_Only = products.Download_Only[i];
				stock.Inventory = products.Inventory[i];
				stock.Stock = products.Stock[i];
				stock.Stock_Warn = products.Stock_Warn[i];
				stock.Allow_Back_Order = products.Allow_Back_Order[i];
				stock.Package = products.Package[i];
				stock.Package_Only = products.Package_Only[i];
				stock.Package_Quantity = products.Package_Quantity[i];
				// custom rule
				stock.Rule = "Custom";
				stock.Minimum = 0;
				stock.Maximum = 2000;
				stock.Increment = 1;
				if (stock.Download_Only eq 0 AND stock.Inventory eq 1 AND stock.Allow_Back_Order eq 0) {
					stock.Rule = ListAppend(stock.Rule, "Inventory");
					if (stock.Stock lt stock.Maximum) {
						stock.Maximum = stock.Stock;
					}
				}
				if (stock.Package eq 1 AND stock.Package_Only eq 1) {
					stock.Rule = ListAppend(stock.Rule, "Package");
					stock.Maximum = Int(stock.Maximum/stock.Package_Quantity) * stock.Package_Quantity;
					stock.Increment = stock.Package_Quantity;
				}
				ret[stock.Product_ID] = stock;
			}
			return ret;
		</cfscript>
	</cffunction>
	
	<cffunction name="checkStock" output="false" access="public" returntype="any">
		<cfargument name="Product_ID" type="numeric" required="no" default="0" />
		<cfargument name="productIDs" type="string" required="no" default="" />
		<cfargument name="Workshop_IDs" type="string" required="yes" />
		<cfargument name="Total_Anticipated_Students" type="numeric" required="yes" />
		<cfargument name="Unique_Volunteers" type="numeric" required="yes" />
		<cfscript>
			var productGateway = new assets.csac.com.gateway.ProductGateway();
			var productStock = productGateway.checkStock(Product_ID = arguments.Product_ID, productIDs = productIDs);
			var ret = StructNew();
			var products = "";
			var stock = "";
			stock.Rule = "";
			stock.Minimum = "";
			stock.Maximum = "";
			stock.Increment = "";
			var i = "";
			var productGateway = new assets.csac.com.gateway.ProductGateway();
			if (arguments.Product_ID gt 0) {
				products = productGateway.query(Product_ID = arguments.Product_ID);
			} else if (Len(Trim(arguments.productIDs)) gt 0) {
				products = productGateway.query(productIDs = arguments.productIDs);
			} else {
				products = productGateway.query(Active = 1);	
			}
			for (i=1; i<=products.RecordCount; i++) {
				stock = StructNew();
				stock.Product_ID = products.Product_ID[i];
				stock.productIDs = arguments.productIDs;
				stock.Workshop_IDs = arguments.Workshop_IDs;
				stock.Total_Anticipated_Students = arguments.Total_Anticipated_Students;
				stock.Unique_Volunteers = arguments.Unique_Volunteers;
				stock.Product = products.Product[i];
				stock.Category = products.Category[i];
				stock.Subcategory = products.Subcategory[i];
				stock.Download_Only = products.Download_Only[i];
				stock.Inventory = products.Inventory[i];
				stock.Stock = products.Stock[i];
				stock.Stock_Warn = products.Stock_Warn[i];
				stock.Allow_Back_Order = products.Allow_Back_Order[i];
				stock.Package = products.Package[i];
				stock.Package_Only = products.Package_Only[i];
				stock.Package_Quantity = products.Package_Quantity[i];
				if (stock.Download_Only eq 0 AND stock.Inventory eq 1 AND stock.Allow_Back_Order eq 0) {
					stock.Rule = ListAppend(stock.Rule, "Inventory");
					if (stock.Stock lt stock.Maximum) {
						stock.Maximum = stock.Stock;
					}
				}
				if (stock.Package eq 1 AND stock.Package_Only eq 1) {
					stock.Rule = ListAppend(stock.Rule, "Package");
					stock.Maximum = Int(stock.Maximum/stock.Package_Quantity) * stock.Package_Quantity;
					stock.Increment = stock.Package_Quantity;
				}
				StructAppend(stock, checkCustomStock(argumentCollection = stock), true);
				ret[stock.Product_ID] = stock;
			}
			return ret;
		</cfscript>
	</cffunction>
	
	<cffunction name="checkCustomStock" returntype="any" hint="Returns custom min/max)">
		<cfargument name="Product_ID" type="numeric" required="yes" />
		<cfargument name="Workshop_IDs" type="string" required="yes" />
		<cfargument name="Total_Anticipated_Students" type="numeric" required="yes" />
		<cfargument name="Unique_Volunteers" type="numeric" required="yes" />
		<cfscript>
			var customStock = {};
			customStock.Rule = "";
			customStock.Minimum = "";
			customStock.Maximum = "";
			// custom rules
			if (arguments.Product_ID eq 13) {
				customStock.Rule = "10-100 * workshopCount";
				customStock.Minimum = 0;
				customStock.Maximum = Int(100 * ListLen(arguments.Workshop_IDs));
			} else if (ListFind("2,3,5,12,16,18,25,26,28,55,56,57,58,59,60,61,63,65,66,67,68,69,70,71,72,80", arguments.Product_ID) gt 0) {
				customStock.Rule = "10-300 * workshopCount";
				customStock.Minimum = 0;
				customStock.Maximum = Int(300 * ListLen(arguments.Workshop_IDs));
			} else if (ListFind("35,36,45,47,48,49,50,51,52", arguments.Product_ID) gt 0) {
				customStock.Rule = "10-500";
				customStock.Minimum = 0;
				customStock.Maximum = 500;
			} else if (ListFind("14,34,44", arguments.Product_ID) gt 0) {
				customStock.Rule = "50-2500";
				customStock.Minimum = 0;
				customStock.Maximum = 2500;
			} else if (arguments.Product_ID eq 22) {
				customStock.Rule = "5-200";
				customStock.Minimum = 0;
				customStock.Maximum = 200;
			} else if (ListFind("7,24", arguments.Product_ID) gt 0) {
				customStock.Rule = "1-totalAnticipatedStudents";
				customStock.Minimum = 0;
				customStock.Maximum = Int(arguments.Total_Anticipated_Students);
			} else if (ListFind("1,8,9,10,11,17,19,20,21,23,29,30", arguments.Product_ID) gt 0) {
				customStock.Rule = "1-Ceiling(1.1 * uniqueVolunteers)";
				customStock.Minimum = 0;
				customStock.Maximum = Int(Ceiling(arguments.Unique_Volunteers * 1.1));
			}
			if (customStock.Maximum lt arguments.Maximum) {
				return customStock;
			} else {
				
			}
		</cfscript>
	</cffunction>
	
	<cffunction name="getProductQuantityRules" returntype="any" hint="Returns min, max and increment quantity for a products. Includes inventory, packaging and custom rules (if applicable)">
		<cfargument name="productIDs" type="string" default="" />
		<cfargument name="Workshop_IDs" type="string" required="yes" />
		<cfargument name="Total_Anticipated_Students" type="numeric" required="yes" />
		<cfargument name="Unique_Volunteers" type="numeric" required="yes" />
		<cfscript>
			var productID = "";
			var workshopCount = ListLen(arguments.Workshop_IDs);
			var totalAnticipatedStudents = arguments.Total_Anticipated_Students;
			var uniqueVolunteers = arguments.Unique_Volunteers;
			// get from csac
			var productGateway = new assets.csac.com.gateway.ProductGateway();
			var productsStock = productGateway.getStock(argumentCollection = arguments);
			for (productID in productsStock) {
				var productRule = "";
				var productMin = "";
				var productMax = "";
				// custom rules
				if (productID eq 13) {
					productRule = "10-100 * workshopCount";
					productMin = 0;
					productMax = Int(100 * workshopCount);
				} else if (ListFind("2,3,5,12,16,18,25,26,28,55,56,57,58,59,60,61,63,65,66,67,68,69,70,71,72,80", productID) gt 0) {
					productRule = "10-300 * workshopCount";
					productMin = 0;
					productMax = Int(300 * workshopCount);
				} else if (ListFind("35,36,45,47,48,49,50,51,52", productID) gt 0) {
					productRule = "10-500";
					productMin = 0;
					productMax = 500;
				} else if (ListFind("14,34,44", productID) gt 0) {
					productRule = "50-2500";
					productMin = 0;
					productMax = 2500;
				} else if (productID eq 22) {
					productRule = "5-200";
					productMin = 0;
					productMax = 200;
				} else if (ListFind("7,24", productID) gt 0) {
					productRule = "1-totalAnticipatedStudents";
					productMin = 0;
					productMax = Int(totalAnticipatedStudents);
				} else if (ListFind("1,8,9,10,11,17,19,20,21,23,29,30", productID) gt 0) {
					productRule = "1-Ceiling(1.1 * uniqueVolunteers)";
					productMin = 0;
					productMax = Int(Ceiling(uniqueVolunteers * 1.1));
				}
				if (Len(Trim(productRule)) > 0 && productMax < productsStock[productID].Maximum) {
					productsStock[productID].Rule = ListAppend(productsStock[productID].Rule, "Custom");
					productsStock[productID].Custom = productRule;
					productsStock[productID].Maximum = productMax;
				}
			}
			return productsStock;
		</cfscript>
	</cffunction>
	
	<cffunction name="_getProductQuantityRules" returntype="any" hint="Returns min, max and increment quantity for a products. Includes inventory, packaging and custom rules (if applicable)">
		<cfargument name="Workshop_IDs" type="string" required="yes" />
		<cfargument name="Total_Anticipated_Students" type="numeric" required="yes" />
		<cfargument name="Unique_Volunteers" type="numeric" required="yes" />
		<cfargument name="productIDs" type="string" default="" />
		<cfargument name="productCategoryIDs" type="string" default="" />
		<cfscript>
			var ret = StructNew();
			// get from csac
			var productGateway = new assets.csac.com.gateway.ProductGateway();
			var qProducts = productGateway.query(productIDs = IIf(Len(Trim(arguments.productIDs)) gt 0, DE(arguments.productIDs), DE("")), productCategoryIDs = IIf(Len(Trim(arguments.productCategoryIDs)) gt 0, DE(arguments.productCategoryIDs), DE("")));
			var sProducts = StructNew();
			var aProducts = ArrayNew(1);
			var sProduct = "";
			var minPQFactor = 1;
			var maxPQFactor = 1;
			for (i=1; i lte qProducts.RecordCount; i++) {
				sProduct = StructNew();
				sProduct["Product_ID"] = qProducts.Product_ID[i];
				sProduct["Deleted"] = qProducts.Deleted[i];
				sProduct["Active"] = qProducts.Active[i];
				sProduct["Inventory"] = qProducts.Inventory[i];
				sProduct["Stock"] = qProducts.Stock[i];
				sProduct["Allow_Back_Order"] = qProducts.Allow_Back_Order[i];
				sProduct["Download_Only"] = qProducts.Download_Only[i];
				sProduct["Package_Only"] = qProducts.Package_Only[i];
				sProduct["Package_Quantity"] = qProducts.Package_Quantity[i];
				sProduct["Workshop_IDs"] = arguments.Workshop_IDs;
				sProduct["Workshop_Count"] = ListLen(sProduct["Workshop_IDs"]);
				sProduct["Total_Anticipated_Students"] = arguments.Total_Anticipated_Students;
				sProduct["Unique_Volunteers"] = arguments.Unique_Volunteers;
				sProduct["Package"] = false;
				sProduct["Package_Rule"] = "";
				sProduct["minPQFactor"] = "";
				sProduct["maxPQFactor"] = "";
				sProduct["Custom"] = false;
				sProduct["Custom_Rule"] = "";
				sProduct["Min"] = "";
				sProduct["Max"] = "";
				
				// custom rules
				if (sProduct["Product_ID"] eq 13) {
					sProduct["Custom"] = true;
					sProduct["Custom_Rule"] = "10-100 * workshopCount";
					sProduct["Min"] = 10 * sProduct["Workshop_Count"];
					sProduct["Max"] = 100 * sProduct["Workshop_Count"];
				} else if (ListFind("2,3,5,12,16,18,25,26,28,55,56,57,58,59,60,61", sProduct["Product_ID"]) gt 0) {
					sProduct["Custom"] = true;
					sProduct["Custom_Rule"] = "10-300 * workshopCount";
					sProduct["Min"] = 10 * sProduct["Workshop_Count"];
					sProduct["Max"] = 300 * sProduct["Workshop_Count"];
				} else if (ListFind("35,36,45,47,48,49,50,51,52", sProduct["Product_ID"]) gt 0) {
					sProduct["Custom"] = true;
					sProduct["Custom_Rule"] = "10-500";
					sProduct["Min"] = 10;
					sProduct["Max"] = 500;
				} else if (ListFind("14,34,44", sProduct["Product_ID"]) gt 0) {
					sProduct["Custom"] = true;
					sProduct["Custom_Rule"] = "50-2500";
					sProduct["Min"] = 50;
					sProduct["Max"] = 2500;
				} else if (sProduct["Product_ID"] eq 22) {
					sProduct["Custom"] = true;
					sProduct["Custom_Rule"] = "5-200";
					sProduct["Min"] = 5;
					sProduct["Max"] = 200;
				} else if (ListFind("7,24", sProduct["Product_ID"]) gt 0) {
					sProduct["Custom"] = true;
					sProduct["Custom_Rule"] = "1-totalAnticipatedStudents";
					sProduct["Min"] = 1;
					sProduct["Max"] = sProduct["Total_Anticipated_Students"];
				} else if (ListFind("1,8,9,10,11,17,19,20,21,23,29,30", sProduct["Product_ID"]) gt 0) {
					sProduct["Custom"] = true;
					sProduct["Custom_Rule"] = "1-Ceiling(1.1 * uniqueVolunteers)";
					sProduct["Min"] = 1;
					sProduct["Max"] = Ceiling(sProduct["Unique_Volunteers"] * 1.1);
				}
				
				// package rules
				if (sProduct["Package_Only"] eq 1) {
					// min
					if (sProduct["Min"] lt sProduct["Package_Quantity"]) {
						sProduct["Min"] = sProduct["Package_Quantity"];
					} else if (sProduct["Min"] gt sProduct["Package_Quantity"]) {
						minPQFactor = sProduct["Min"] / sProduct["Package_Quantity"];
						if (minPQFactor lt 1.5) {
							sProduct["Min"] = sProduct["Package_Quantity"];
						} else if (minPQFactor gte 1.5) {
							sProduct["Custom"] = false;
							sProduct["Package"] = true;
							sProduct["Package_Rule"] = "Ceiling(minPQFactor * Package_Quantity)";
							sProduct["minPQFactor"] = minPQFactor;
							sProduct["Min"] = Ceiling(minPQFactor * sProduct["Package_Quantity"]);
						}
					}
					// max
					if (sProduct["Max"] lt sProduct["Package_Quantity"]) {
						sProduct["Max"] = sProduct["Package_Quantity"];
					} else if (sProduct["Max"] gt sProduct["Package_Quantity"]) {
						maxPQFactor = sProduct["Max"] / sProduct["Package_Quantity"];
						if (maxPQFactor lt 1.5) {
							sProduct["Max"] = sProduct["Package_Quantity"];
						} else if (maxPQFactor gte 1.5) {
							sProduct["Custom"] = false;
							sProduct["Package"] = true;
							sProduct["Package_Rule"] = "Ceiling(maxPQFactor * Package_Quantity)";
							sProduct["maxPQFactor"] = maxPQFactor;
							sProduct["Max"] = Ceiling(maxPQFactor * sProduct["Package_Quantity"]);
						}
					}
				}
				ArrayAppend(aProducts, sProduct);
				sProducts[sProduct["Product_ID"]] = sProduct;
			}
			ret.productsArray = aProducts;
			ret.productsStruct = sProducts;
			return ret;
		</cfscript>
	</cffunction>
	
	<cffunction name="getOrderStatuses" returntype="array">
		<cfset var orderStatuses = "">
		<cfinvoke component="assets.csac.com.Base" method="getOrderStatuses" returnvariable="orderStatuses" />
		<cfreturn orderStatuses>
	</cffunction>
	
	<cffunction name="getCSACOrderStatuses" returntype="array">
		<cfset var orderStatuses = "">
		<cfinvoke component="assets.csac.com.Base" method="getOrderStatuses" returnvariable="orderStatuses" />
		<cfreturn orderStatuses>
	</cffunction>
	
	<cffunction name="genResponseObj" access="public" output="false" returntype="struct">
		<cfset var responseObj = StructNew() />
		<cfset responseObj["success"] = true />
		<cfset responseObj["message"] = "" />
		<cfset responseObj["data"] = "" />
		<cfset responseObj["recordcount"] = "" />
		<cfreturn responseObj />
	</cffunction>
	
	 <cffunction name="validateRequest" access="public" returntype="boolean">
		<cfreturn true />
	</cffunction>

	<cffunction name="validUser" access="public" returntype="boolean">
		<cfset var ret = true />
		<cfset var cApplication = "" />
		<cfset var cSession = "" />
		<cflock scope="session" timeout="20" type="readonly">
			<cfset cSession = StructCopy(session) />
		</cflock>
		<cfif IsUserLoggedIn()>
			<cflock scope="application" timeout="20" type="readonly">
				<cfset cApplication = StructCopy(application) />
			</cflock>
			<cfif NOT IsStruct(cSession.authUser) or NOT StructKeyExists(cSession.authUser, "lastDateTime") OR NOT StructKeyExists(cSession.authUser, "userId")>
				<cfset ret = false />
			<cfelseif NOT IsDate(cSession.authUser.lastDateTime) or cApplication.initDateTime gt cSession.authUser.lastDateTime>
				<cfset ret = false />
			<cfelseif NOT IsNumeric(cSession.authUser.userId) or cSession.authUser.userId eq 0>
				<cfset ret = false />
			</cfif>
		<cfelse>
			<cfif StructKeyExists(cSession, "authUser")>
				<cfset ret = false />
			</cfif>
		</cfif>
		<cfreturn ret />
	</cffunction>
	
	<cffunction name="titleCase2" returntype="string">
		<cfargument name="str" default="">
		<cfset var ret = "" />
		<cfset var ret1 = "">
		<cfset var ret2 = "">
		<cfset var ret3 = "">
		<cfset var ret4 = "">
		<cfset var ret5 = "">
		<cfset var ret6 = "">
		<cfif Len(Trim(arguments.str)) gt 0>
			<!--- space --->
			<cfif ListLen(arguments.str, " ") gt 1>
				<cfset loopCount = 1>
				<cfloop index="w" list="#arguments.str#" delimiters=" ">
					<cfif loopCount gt 1>
						<cfset ret1 = ret1 & " ">
					</cfif>
					<cfset wTrim = Trim(w) />
					<cfset firstLetter = UCase(Left(wTrim, 1))>
					<cfset restOfWord = LCase(Mid(wTrim, 2, Len(wTrim)))>
					<cfset ret1 = ret1 & firstLetter & restOfWord>
					<cfset loopCount = loopCount + 1>
				</cfloop>
				<cfset ret1 = Trim(ret1)>
			<cfelse>
				<cfset wTrim = Trim(arguments.str) />
				<cfset firstLetter = UCase(Left(wTrim, 1))>
				<cfset restOfWord = LCase(Mid(wTrim, 2, Len(wTrim)))>
				<cfset ret1 = firstLetter & restOfWord>
			</cfif>
			
			<!--- dash --->
			<cfif ListLen(ret1, "-") gt 1>
				<cfset loopCount = 1>
				<cfloop index="w" list="#ret1#" delimiters="-">
					<cfif loopCount gt 1>
						<cfset ret2 = ret2 & "-">
					</cfif>
					<cfset wTrim = Trim(w) />
					<cfset firstLetter = UCase(Left(wTrim, 1))>
					<cfset restOfWord = LCase(Mid(wTrim, 2, Len(wTrim)))>
					<cfset ret2 = ret2 & firstLetter & restOfWord>
					<cfset loopCount = loopCount + 1>
				</cfloop>
				<cfset ret2 = Trim(ret2)>
			<cfelse>
				<cfset ret2 = ret1>
			</cfif>
			
			<!--- single quote --->
			<cfif ListLen(ret2, "'") gt 1>
				<cfset loopCount = 1>
				<cfloop index="w" list="#ret2#" delimiters="'">
					<cfif loopCount gt 1>
						<cfset ret3 = ret3 & "'">
					</cfif>
					<cfset wTrim = Trim(w) />
					<cfset firstLetter = UCase(Left(wTrim, 1))>
					<cfset restOfWord = LCase(Mid(wTrim, 2, Len(wTrim)))>
					<cfset ret3 = ret3 & firstLetter & restOfWord>
					<cfset loopCount = loopCount + 1>
				</cfloop>
				<cfset ret3 = Trim(ret3)>
			<cfelse>
				<cfset ret3 = ret2>
			</cfif>
			<cfreturn ret3>
		<cfelse>
			<cfreturn "">
		</cfif>
	</cffunction>
--->